From c57c012a64ef93bef550ac3c3778428391f2ceaa Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 8 Sep 2020 16:30:11 -0400 Subject: [PATCH 01/34] Infrastructure: Trigger regression testing for lock file changes (pull #1516) If a PR changes only a lock file, run regression testing. --- scripts/regression-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/regression-tests.sh b/scripts/regression-tests.sh index f643d7880d..c57ac8a390 100755 --- a/scripts/regression-tests.sh +++ b/scripts/regression-tests.sh @@ -27,7 +27,7 @@ TEST_INFRA=$(git diff --name-only $COMMIT_RANGE | grep -oP 'test/(util|index)') EXAMPLE_DIRS=$(git diff --name-only $COMMIT_RANGE | grep -oP 'examples/\K[\w-]+(?=/)' | uniq) EXAMPLE_INFRA=$(echo "$EXAMPLE_DIRS" | grep -P '^(js|css)$') -PACKAGE_UPDATE=$(git diff --name-only $COMMIT_RANGE | grep -P '(package\.json)') +PACKAGE_UPDATE=$(git diff --name-only $COMMIT_RANGE | grep -P 'package(-lock)?\.json') if [[ $TEST_INFRA || $EXAMPLE_INFRA || $PACKAGE_UPDATE ]] then From 32ca9d325ae96086880c57c5b3b250745d99884b Mon Sep 17 00:00:00 2001 From: Valerie Young Date: Tue, 15 Sep 2020 09:01:11 -0700 Subject: [PATCH 02/34] Infrastructure: Add support for opening examples in CodePen (pull #1110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves issue #1102 by adding support to dynamically add an "Open in Codepen" button adjacent to the heading above an example. The examples/js/examples.js script uses the Codepen POST API to prefill its HTML/CSS/JS inputs. The script expects a specific class on the heading of the example and requires that IDs for specific elements be passed to the scourceCode.add method call. Co-authored-by: Matt King Co-authored-by: Zoë Bijl --- .../coding-template/Example-Template.html | 8 +- examples/css/core.css | 69 +++++++++++ examples/js/examples.js | 108 +++++++++++++++++- 3 files changed, 181 insertions(+), 4 deletions(-) diff --git a/examples/coding-template/Example-Template.html b/examples/coding-template/Example-Template.html index f5b5c932eb..ef7cc2ff64 100644 --- a/examples/coding-template/Example-Template.html +++ b/examples/coding-template/Example-Template.html @@ -57,7 +57,9 @@

EXAMPLE_NAME Example

-

Example

+
+

Example

+
-
    +
    • CSS: example_name.css @@ -212,7 +214,7 @@

      HTML Source Code

      If you change the ID of either the 'ex1' div or the 'sc1' pre, be sure to update the sourceCode.add function parameters. -->
diff --git a/examples/css/core.css b/examples/css/core.css index 2179ae63f4..3ce62a3541 100644 --- a/examples/css/core.css +++ b/examples/css/core.css @@ -40,3 +40,72 @@ table.data.attributes tbody th, table.data.attributes tbody td { border: 1px solid silver; } + +/* CodePen button */ +.example-header { + display: flex; + align-items: center; + margin-top: 3rem; + page-break-after: avoid; + page-break-inside: avoid; + font: 100% sans-serif; + font-family: inherit; + line-height: 1.2; + hyphens: manual; +} + +.example-header > :first-child { + margin: 0; +} + +.example-header > :first-child + * { + margin-left: 1em; +} + +.example-header button { + display: inline-block; + position: relative; + padding: 0.4em 0.7em; + border: 1px solid hsl(213, 71%, 49%); + border-radius: 5px; + box-shadow: 0 1px 2px hsl(216, 27%, 55%); + color: #fff; + font-size: inherit; + text-shadow: 0 -1px 1px hsl(216, 27%, 25%); + background-color: hsl(216, 82%, 51%); + background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%)); +} + +.example-header button:hover { + border-color: hsl(213, 71%, 29%); + background-color: hsl(216, 82%, 31%); + background-image: linear-gradient(to bottom, hsl(216, 82%, 33%), hsl(216, 82%, 27%)); + cursor: default; +} + +.example-header button:focus { + outline: none; +} + +.example-header button:focus::before { + position: absolute; + z-index: -1; + + /* button border width - outline width - offset */ + top: calc(-1px - 3px - 3px); + right: calc(-1px - 3px - 3px); + bottom: calc(-1px - 3px - 3px); + left: calc(-1px - 3px - 3px); + border: 3px solid hsl(213, 71%, 49%); + + /* button border radius + outline width + offset */ + border-radius: calc(5px + 3px + 3px); + content: ''; +} + +.example-header button:active { + border-color: hsl(213, 71%, 49%); + background-color: hsl(216, 82%, 31%); + background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%)); + box-shadow: inset 0 3px 5px 1px hsl(216, 82%, 30%); +} diff --git a/examples/js/examples.js b/examples/js/examples.js index d0a2c21a20..10aa54ba6f 100644 --- a/examples/js/examples.js +++ b/examples/js/examples.js @@ -35,17 +35,26 @@ var VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', aria.widget.SourceCode = function () { this.location = new Array(); this.code = new Array(); + this.exampleHeader = new Array(); + this.resources = new Array(); }; /** * Adds source code * + * @param {string} locationId - ID of `code` element that will display the example html + * @param {string} codeID - ID of element containing only and all of the html used to render the example widget + * @param {string} exampleHeaderId - ID of header element under which the "Open in Codepen" button belongs + * @param {string} cssJsFilesId - ID of element containing links to all the relevent js and css files used for the example widget + * * @method add * @memberof aria.widget.SourceCode */ -aria.widget.SourceCode.prototype.add = function (locationId, codeId) { +aria.widget.SourceCode.prototype.add = function (locationId, codeId, exampleHeaderId, cssJsFilesId) { this.location[this.location.length] = locationId; this.code[this.code.length] = codeId; + this.exampleHeader[this.exampleHeader.length] = exampleHeaderId; + this.resources[this.resources.length] = cssJsFilesId; }; /** @@ -66,6 +75,11 @@ aria.widget.SourceCode.prototype.make = function () { if (sourceCodeNode.innerHTML.startsWith('
')) { sourceCodeNode.innerHTML = sourceCodeNode.innerHTML.replace('
', ''); } + + // Adds the "Open In CodePen" button by the example header + if (this.exampleHeader[i]) { + addOpenInCodePenForm(i, this.exampleHeader[i], this.code[i], this.resources[i]); + } } }; @@ -300,4 +314,96 @@ function indentLines (input, indentation) { return lines.join('\n'); } +/** + * Creates and adds an "Open in CodePen" button + * + * @param {String} exampleIndex - the example number, if there are multiple examples + * @param {String} exampleHeaderId - the example header to place the button next to + * @param {String} exampleCodeId - the example html code + * @param {String} exampleFilesId - the element containing all relevent CSS and JS file + */ +function addOpenInCodePenForm (exampleIndex, exampleHeaderId, exampleCodeId, exampleFilesId) { + var jsonInputId = 'codepen-data-ex-' + exampleIndex; + var buttonId = exampleCodeId + '-codepenbutton' + + var form = document.createElement('form'); + form.setAttribute('action', 'https://codepen.io/pen/define'); + form.setAttribute('method', 'POST'); + form.setAttribute('target', '_blank'); + + var input = document.createElement('input'); + input.setAttribute('id', jsonInputId); + input.setAttribute('type', 'hidden'); + input.setAttribute('name', 'data'); + + var button = document.createElement('button'); + button.innerText = 'Open In CodePen'; + + form.appendChild(input); + form.appendChild(button); + + var exampleHeader = document.getElementById(exampleHeaderId); + exampleHeader.parentNode.insertBefore(form, exampleHeader.nextSibling); + + // Correct the indentation for the example html + var indentedExampleHtml = document.getElementById(exampleCodeId).innerHTML; + indentedExampleHtml = indentedExampleHtml.replace(/^\n+/, ''); + var indentation = indentedExampleHtml.match(/^\s+/)[0]; + var exampleHtml = indentedExampleHtml.replace(new RegExp('^' + indentation, 'gm'), ''); + + var postJson = { + html: exampleHtml, + css: '', + js: '', + head: '' + }; + + var totalFetchedFiles = 0; + var fileLinks = document.querySelectorAll('#' + exampleFilesId + ' a'); + + for (let fileLink of fileLinks) { + + var request = new XMLHttpRequest(); + + request.open('GET', fileLink.href, true); + request.onload = function() { + var href = this.responseURL; + if (this.status >= 200 && this.status < 400) { + if (href.indexOf('css') !== -1) { + postJson.css = postJson.css.concat(this.response); + } + if (href.indexOf('js') !== -1) { + postJson.js = postJson.js.concat(this.response); + } + totalFetchedFiles++; + } + else { + hideButton(buttonId, "Could not load resource: " + href); + } + }; + request.onerror = function() { + hideButton(buttonId, "Could not load resource: " + fileLink.href); + }; + request.send(); + } + + var timerId = setInterval(() => { + console.log(totalFetchedFiles); + if (totalFetchedFiles === fileLinks.length) { + document.getElementById(jsonInputId).value = JSON.stringify(postJson); + clearInterval(timerId); + } + }, 500); + + setTimeout(() => { + clearInterval(timerId); + }, 10000); +} + +function hideButton(buttonId, errorMsg) { + let button = document.querySelector(buttonId); + button.style.display = "none"; + console.log("Removing 'Open in Codepen button'. " + errorMsg); +} + var sourceCode = new aria.widget.SourceCode(); From 7e78d9a43f49fbfbec071e5fcdff24a3dbe9395c Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 25 Sep 2020 14:44:46 -0400 Subject: [PATCH 03/34] Infrastructure: Move Stylelint to GitHub action and fix broken config (pull #1518) * chore: Use GitHub Actions to run Stylelint * Includes problem matcher to show inline error messages on PRs * fix: Stylelint config was invalid * fix: Use auto-fix for stylelint issues * fix(stylelint): Unexpected duplicate selector * fix(stylelint): max-empty-lines * chore(styeling): Disable no-descending-specificity --- .github/workflows/lint-css.yml | 36 ++++++++++++++++ .stylelintrc | 12 +++++- .stylelintrc.js | 3 -- .travis.yml | 3 -- examples/carousel/css/carousel-prev-next.css | 43 +++++-------------- examples/carousel/css/carousel-tablist.css | 20 +++------ examples/combobox/css/combobox-datepicker.css | 20 +++------ examples/combobox/css/select-only.css | 4 +- .../dialog-modal/css/datepicker-dialog.css | 10 ++--- examples/menubar/css/menubar-editor.css | 7 +-- examples/menubar/css/menubar-navigation.css | 19 +++----- examples/radio/css/radio.css | 2 +- 12 files changed, 84 insertions(+), 95 deletions(-) create mode 100644 .github/workflows/lint-css.yml delete mode 100644 .stylelintrc.js diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml new file mode 100644 index 0000000000..d3bac1e274 --- /dev/null +++ b/.github/workflows/lint-css.yml @@ -0,0 +1,36 @@ +name: CSS Linting +on: + push: + paths: + - "package*.json" + - ".stylelint*" + - "**/*.css" + - ".github/workflows/lint-css.yml" + - "!common/**" + + pull_request: + paths: + - "package*.json" + - ".stylelint*" + - "**/*.css" + - ".github/workflows/lint-css.yml" + - "!common/**" + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v1 + + - uses: xt0rted/stylelint-problem-matcher@v1 + + - name: Install npm dependencies + run: npm ci + + - name: Stylelint + run: npm run lint:css diff --git a/.stylelintrc b/.stylelintrc index 1796dab3a0..c0864c943b 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,6 +1,14 @@ { + "extends": "stylelint-config-standard", "rules": { - "media-feature-name-no-unknown": [true, - "ignoreMediaFeatureNames": ["forced-colors"] ] + "media-feature-name-no-unknown": [ + true, + { + "ignoreMediaFeatureNames": [ + "forced-colors" + ] + } + ], + "no-descending-specificity": null } } diff --git a/.stylelintrc.js b/.stylelintrc.js deleted file mode 100644 index e92d12fe4c..0000000000 --- a/.stylelintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: ['stylelint-config-standard'] -}; diff --git a/.travis.yml b/.travis.yml index cacaf643c2..10488f3318 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,6 @@ jobs: - env: ALLOW_FAILURE=true include: - - stage: Lint - name: CSS Linting - script: npm run lint:css - stage: Lint name: JS Linting script: npm run lint:es diff --git a/examples/carousel/css/carousel-prev-next.css b/examples/carousel/css/carousel-prev-next.css index d85789b047..72bce6b06c 100644 --- a/examples/carousel/css/carousel-prev-next.css +++ b/examples/carousel/css/carousel-prev-next.css @@ -21,7 +21,7 @@ img.reload { padding: 5px; } -.carousel .carousel-items.focus { +.carousel .carousel-items.focus { padding: 2px; border: solid 3px #005a9c; } @@ -48,10 +48,6 @@ img.reload { .carousel .carousel-item .carousel-caption a { cursor: pointer; -} - - -.carousel .carousel-item .carousel-caption a { text-decoration: underline; color: #fff; font-weight: 600; @@ -62,7 +58,6 @@ img.reload { display: inline-block; margin: 0; padding: 6px; - display: inline-block; background-color: rgba(0, 0, 0, 0.65); border-radius: 5px; border: 0 solid transparent; @@ -78,6 +73,8 @@ img.reload { border: 2px solid #fff; background-color: rgba(0, 0, 0, 1); outline: none; + border-width: 2px solid #fff; + color: #fff; } .carousel .carousel-item .carousel-caption p { @@ -112,6 +109,12 @@ img.reload { .carousel .controls button { position: absolute; z-index: 10; + flex: 0 0 auto; + margin: 0; + padding: 0; + border: none; + background: transparent; + outline: none; } .carousel .controls button.previous { @@ -122,20 +125,8 @@ img.reload { right: 18px; } - /* SVG Controls */ - -.carousel .controls button { - flex: 0 0 auto; - margin: 0; - padding: 0; - border: none; - background: transparent; - outline: none; - z-index: 10; -} - .carousel .controls svg .background { stroke: black; fill: black; @@ -171,7 +162,6 @@ img.reload { stroke: white; } - .carousel .controls svg polygon { fill: white; stroke: white; @@ -204,7 +194,6 @@ img.reload { height: 36px; } - .carousel.carousel-moreaccessible .controls button.previous { right: 60px; } @@ -214,8 +203,8 @@ img.reload { } .carousel-moreaccessible .carousel-items, -.carousel-moreaccessible .carousel-items.focus { - padding: 0px; +.carousel-moreaccessible .carousel-items.focus { + padding: 0; border: none; } @@ -258,15 +247,6 @@ img.reload { border-radius: 5px; } -.carousel .carousel-item .carousel-caption a:focus { - padding: 4px; - border-width: 2px solid #fff; - background-color: rgba(0, 0, 0, 1); - color: #fff; - outline: none; -} - - .carousel-moreaccessible .carousel-item .carousel-caption span.contrast, .carousel-moreaccessible .carousel-item .carousel-caption span.contrast:hover { background-color: transparent; @@ -303,4 +283,3 @@ img.reload { left: 0; padding: 0.25em 0.25em 0; } - diff --git a/examples/carousel/css/carousel-tablist.css b/examples/carousel/css/carousel-tablist.css index 064dde0d46..bc139c3383 100644 --- a/examples/carousel/css/carousel-tablist.css +++ b/examples/carousel/css/carousel-tablist.css @@ -21,7 +21,7 @@ img.reload { padding: 5px; } -.carousel-tablist .carousel-items.focus { +.carousel-tablist .carousel-items.focus { padding: 2px; border: solid 3px #005a9c; } @@ -73,6 +73,8 @@ img.reload { border: 2px solid #eee; background-color: rgba(0, 0, 0, 1); outline: none; + border-width: 2px solid #fff; + color: #fff; } .carousel-tablist .carousel-item .carousel-caption p { @@ -226,7 +228,7 @@ img.reload { .carousel-tablist [role="tab"]:focus circle.border { display: block; fill: #005a9c; - stroke: #ffffff; + stroke: #fff; } .carousel-tablist [role="tablist"].focus circle.tab-background { @@ -261,10 +263,9 @@ img.reload { border-radius: 5px; } - .carousel-tablist-moreaccessible .carousel-items, -.carousel-tablist-moreaccessible .carousel-items.focus { - padding: 0px; +.carousel-tablist-moreaccessible .carousel-items.focus { + padding: 0; border: none; } @@ -307,15 +308,6 @@ img.reload { border-radius: 5px; } -.carousel-tablist .carousel-item .carousel-caption a:focus { - padding: 4px; - border-width: 2px solid #fff; - background-color: rgba(0, 0, 0, 1); - color: #fff; - outline: none; -} - - .carousel-tablist-moreaccessible .carousel-item .carousel-caption span.contrast, .carousel-tablist-moreaccessible .carousel-item .carousel-caption span.contrast:hover { background-color: transparent; diff --git a/examples/combobox/css/combobox-datepicker.css b/examples/combobox/css/combobox-datepicker.css index 5897681781..5370b4edff 100644 --- a/examples/combobox/css/combobox-datepicker.css +++ b/examples/combobox/css/combobox-datepicker.css @@ -5,17 +5,14 @@ .combobox-datepicker .group { display: inline-flex; + position: relative; + width: 12.125rem; } .combobox-datepicker label { display: block; } -.combobox-datepicker .group { - position: relative; - width: 12.125rem; -} - .combobox-datepicker .group input, .combobox-datepicker .group button { background-color: white; @@ -62,7 +59,7 @@ .combobox-datepicker .group.focus input, .combobox-datepicker .group.focus button { - background-color: #DEF; + background-color: #def; } .combobox-datepicker .group polygon { @@ -123,6 +120,9 @@ .combobox-datepicker .dates { width: 320px; + padding-left: 1em; + padding-right: 1em; + padding-top: 1em; } .combobox-datepicker .prev-year, @@ -183,7 +183,6 @@ border: 1px solid black; } - .combobox-datepicker .fa-calendar-alt { color: hsl(216, 89%, 51%); } @@ -194,12 +193,6 @@ text-align: center; } -.combobox-datepicker .dates { - padding-left: 1em; - padding-right: 1em; - padding-top: 1em; -} - .combobox-datepicker .dates th, .combobox-datepicker .dates td { text-align: center; @@ -220,7 +213,6 @@ background: #eee; } - .combobox-datepicker .dates td[aria-selected] { padding: 1px; border: 2px dotted black; diff --git a/examples/combobox/css/select-only.css b/examples/combobox/css/select-only.css index 6eeaf22e7a..cfb8fc0e65 100644 --- a/examples/combobox/css/select-only.css +++ b/examples/combobox/css/select-only.css @@ -60,7 +60,7 @@ border-radius: 0 0 4px 4px; display: none; max-height: 300px; - overflow-y:scroll; + overflow-y: scroll; left: 0; position: absolute; top: 100%; @@ -100,4 +100,4 @@ top: 50%; transform: translate(0, -50%) rotate(45deg); width: 8px; -} \ No newline at end of file +} diff --git a/examples/dialog-modal/css/datepicker-dialog.css b/examples/dialog-modal/css/datepicker-dialog.css index 89b43d7612..180e85971d 100644 --- a/examples/dialog-modal/css/datepicker-dialog.css +++ b/examples/dialog-modal/css/datepicker-dialog.css @@ -33,7 +33,7 @@ top: 0.25em; margin: 0; padding: 4px; - border: 0px solid #005a9c; + border: 0 solid #005a9c; background-color: white; border-radius: 5px; } @@ -45,18 +45,18 @@ } .datepicker .fa-calendar-alt { - color: hsl(216, 89%, 51%); + color: hsl(216, 89%, 51%); } .datepicker button.icon:focus { outline: none; padding: 2px; border-width: 2px; - background-color: #DEF; + background-color: #def; } .datepicker input:focus { - background-color: #DEF; + background-color: #def; outline: 2px solid #005a9c; outline-offset: 1px; } @@ -223,7 +223,6 @@ border: 1px solid rgb(100, 100, 100); } - .datepicker-dialog table.dates td[aria-selected] { padding: 1px; border: 2px dotted rgb(100, 100, 100); @@ -239,7 +238,6 @@ color: white; } - .datepicker-dialog .dialog-message { padding-top: 0.25em; padding-left: 1em; diff --git a/examples/menubar/css/menubar-editor.css b/examples/menubar/css/menubar-editor.css index 01f257cbe1..edcaa3cca3 100644 --- a/examples/menubar/css/menubar-editor.css +++ b/examples/menubar/css/menubar-editor.css @@ -54,7 +54,7 @@ .menubar-editor [role="menubar"] > li > [role="menuitem"][aria-expanded="true"]::after { content: url('../images/up-arrow-focus.svg'); - } +} .menubar-editor [role="menubar"] [role="menu"] { display: none; @@ -81,7 +81,7 @@ .menubar-editor [role="menubar"] [role="separator"] { padding: 6px; background-color: #eee; - border: 0px solid #eee; + border: 0 solid #eee; color: black; } @@ -124,10 +124,8 @@ padding-right: 3px; } - /* focus and hover styling */ - .menubar-editor [role="menubar"] [role="menuitem"]:focus, .menubar-editor [role="menubar"] [role="menuitemcheckbox"]:focus, .menubar-editor [role="menubar"] [role="menuitemradio"]:focus { @@ -148,7 +146,6 @@ padding-right: 3px; } - .menubar-editor [role="menubar"] [role="menuitem"]:hover { padding: 4px; border: 2px solid #034575; diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 90b8f004b2..54361e90c7 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -22,7 +22,7 @@ position: relative; } -.menubar-navigation > li li { +.menubar-navigation > li li { display: block; } @@ -37,7 +37,7 @@ .menubar-navigation [role="separator"] { padding: 6px; background-color: #eee; - border: 0px solid #eee; + border: 0 solid #eee; color: black; border-radius: 5px; } @@ -74,6 +74,10 @@ position: absolute; margin: 0; padding: 0; + padding: 7px 4px; + border: 2px solid #034575; + border-radius: 5px; + background-color: #eee; } .menubar-navigation [role="group"] { @@ -81,10 +85,6 @@ padding: 0; } -.menubar-navigation [role="menu"] { - display: none; -} - .menubar-navigation [role="separator"] { padding-top: 3px; background-image: url('../images/separator.svg'); @@ -99,13 +99,6 @@ border: #034575 solid 2px; } -.menubar-navigation [role="menu"] { - padding: 7px 4px; - border: 2px solid #034575; - border-radius: 5px; - background-color: #eee; -} - .menubar-navigation [role="menuitem"][aria-expanded="true"], .menubar-navigation [role="menuitem"]:focus, .menubar-navigation [role="menuitem"]:hover { diff --git a/examples/radio/css/radio.css b/examples/radio/css/radio.css index 962758e132..6054273040 100644 --- a/examples/radio/css/radio.css +++ b/examples/radio/css/radio.css @@ -12,7 +12,7 @@ padding: 4px; padding-left: 30px; padding-right: 8px; - border: 0px solid transparent; + border: 0 solid transparent; border-radius: 5px; display: inline-block; position: relative; From 8f0b40bf3b8e3689322466223c4addfd247327c1 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 25 Sep 2020 17:36:29 -0400 Subject: [PATCH 04/34] Infrastructure: Switch regression test to GitHub Actions (pull #1515) --- .github/workflows/regression.yml | 56 ++++++++++++++++++++++++++++++++ .travis.yml | 29 ----------------- scripts/regression-tests.sh | 12 ------- 3 files changed, 56 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/regression.yml diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml new file mode 100644 index 0000000000..57e02c9c95 --- /dev/null +++ b/.github/workflows/regression.yml @@ -0,0 +1,56 @@ +name: Run regression tests + +on: + pull_request: + paths: + - ".github/workflows/regression.yml" + - "package*.json" + - "test/**" + - "examples/**" + - "!examples/landmarks/**" + push: + branches-ignore: + - master + paths: + - ".github/workflows/regression.yml" + - "package*.json" + - "test/**" + - "examples/**" + - "!examples/landmarks/**" + +jobs: + deploy: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + CI_NODE_INDEX: [0, 1, 2, 3, 4] + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v1 + + - name: Install npm dependencies + run: npm ci + + - name: Run Ava on changed files in PR + run: ./scripts/regression-tests.sh + if: github.event_name == 'pull_request' + env: + COMMIT_RANGE: "origin/${{ github.base_ref }}...${{ github.sha }}" + CI_NODE_TOTAL: 5 + CI_NODE_INDEX: ${{ matrix.CI_NODE_INDEX }} + TEST_WAIT_TIME: 10000 + + - name: Run Ava for changed files from origin branch + run: ./scripts/regression-tests.sh + if: github.event_name == 'push' + env: + COMMIT_RANGE: "origin/master...${{ github.ref }}" + CI_NODE_TOTAL: 5 + CI_NODE_INDEX: ${{ matrix.CI_NODE_INDEX }} + TEST_WAIT_TIME: 10000 diff --git a/.travis.yml b/.travis.yml index 10488f3318..dabc99bfdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ git: stages: - Lint -- Test jobs: fast_finish: true @@ -30,31 +29,3 @@ jobs: name: Regression Tests Coverage Report script: node test/util/report.js env: ALLOW_FAILURE=true - - - stage: Test - name: AVA Regression Tests - addons: - firefox: latest - script: scripts/regression-tests.sh - env: - - TEST_WAIT_TIME=1000 - - CI_NODE_TOTAL=3 - - CI_NODE_INDEX=0 - - stage: Test - name: AVA Regression Tests - addons: - firefox: latest - script: scripts/regression-tests.sh - env: - - TEST_WAIT_TIME=1000 - - CI_NODE_TOTAL=3 - - CI_NODE_INDEX=1 - - stage: Test - name: AVA Regression Tests - addons: - firefox: latest - script: scripts/regression-tests.sh - env: - - TEST_WAIT_TIME=1000 - - CI_NODE_TOTAL=3 - - CI_NODE_INDEX=2 diff --git a/scripts/regression-tests.sh b/scripts/regression-tests.sh index c57ac8a390..b36e3300e1 100755 --- a/scripts/regression-tests.sh +++ b/scripts/regression-tests.sh @@ -4,18 +4,6 @@ if [[ "$CI" != "true" ]] then # When running this script locally, compare the current branch to master COMMIT_RANGE="..master" - -elif [[ "$TRAVIS_PULL_REQUEST" != "false" ]] -then - # If we are on a PR build, we can use TRAVIS_COMMIT_RANGE - COMMIT_RANGE=$TRAVIS_COMMIT_RANGE - -else - # If we are on a branch build, and it has been force pushed, then TRAVIS_PULL_REQUEST will - # not contain useful information for the branch build. - COMMIT_RANGE="origin/master...$TRAVIS_BRANCH" - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" - git fetch origin master fi AVACMD="npm run regression -- -t" From 3c853b8436accfa5798ad17bd1ce660058e1aba5 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 29 Sep 2020 04:39:55 -0400 Subject: [PATCH 05/34] Infrastructure: Swap to GitHub Action for JS linting (pull #1501) Allows lint errors to show as inline comments on PRs. --- .github/workflows/lint-js.yml | 36 +++++++++++++++++++++++++++++++++++ .travis.yml | 3 --- 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/lint-js.yml diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml new file mode 100644 index 0000000000..7360b337e8 --- /dev/null +++ b/.github/workflows/lint-js.yml @@ -0,0 +1,36 @@ +name: JavaScript Linting +on: + push: + paths: + - "package*.json" + - ".eslint*" + - "**/*.js" + - ".github/workflows/lint-js.yml" + - "!common/**" + + pull_request: + paths: + - "package*.json" + - ".eslint*" + - "**/*.js" + - ".github/workflows/lint-js.yml" + - "!common/**" + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v2 + + # setup-node task is used without a particular version in order to load + # the ESLint problem matchers + - name: Set up Node.js + uses: actions/setup-node@v1 + + - name: Install npm dependencies + run: npm ci + + - name: ESLint + run: npm run lint:es diff --git a/.travis.yml b/.travis.yml index dabc99bfdf..df0ce7d247 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,9 +15,6 @@ jobs: - env: ALLOW_FAILURE=true include: - - stage: Lint - name: JS Linting - script: npm run lint:es - stage: Lint name: HTML Linting script: npm run vnu-jar From 7dee05dcfcc6cdf034c79ffa4f6b04dd5919e24b Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 29 Sep 2020 04:58:49 -0400 Subject: [PATCH 06/34] Infrastructure: Create Dependabot config file (pull #1505) * Create Dependabot config file Enables dependabot to auto-upgrade NPM packages. * chore: Only build DependaBot PRs * Skip the double-build of Dependabot pushed branches Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Matt King --- .github/dependabot.yml | 14 ++++++++++++++ .github/workflows/lint-css.yml | 2 ++ .github/workflows/regression.yml | 1 + 3 files changed, 17 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..46ac580db6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "10:00" + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + time: "10:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index d3bac1e274..6184d9494c 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -1,6 +1,8 @@ name: CSS Linting on: push: + branches-ignore: + - "dependabot/**" paths: - "package*.json" - ".stylelint*" diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 57e02c9c95..ece0abea1a 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -11,6 +11,7 @@ on: push: branches-ignore: - master + - "dependabot/**" paths: - ".github/workflows/regression.yml" - "package*.json" From 2233e5cd4fc32de8602a10be8ef73104c04a6bd4 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 29 Sep 2020 11:13:18 -0400 Subject: [PATCH 07/34] Infrastructure: Move Regression coverage to PR comment (pull #1517) --- .github/workflows/coverage-report.yml | 60 +++++++++++++++++++++++++++ .travis.yml | 4 -- 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/coverage-report.yml diff --git a/.github/workflows/coverage-report.yml b/.github/workflows/coverage-report.yml new file mode 100644 index 0000000000..98a2b371de --- /dev/null +++ b/.github/workflows/coverage-report.yml @@ -0,0 +1,60 @@ +name: Regression Tests Coverage Report + +on: + pull_request_target: + paths: + - "examples/**" + - "test/**" + - "!examples/landmarks/**" + +jobs: + report: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + ref: refs/pull/${{ github.event.pull_request.number }}/head + + - name: Install dependencies + run: npm ci + + - name: Run coverage report + run: | + node test/util/report.js >> coverage.log || true + + - name: Comment on PR + uses: actions/github-script@0.9.0 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const fs = require('fs'); + const commentBody = '```' + fs.readFileSync('coverage.log', 'utf8') + '```'; + + if (commentBody.length === 0) { + return + } + // Get the existing comments. + const {data: comments} = await github.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + }) + // Find any comment already made by the bot. + const botComment = comments.find(comment => comment.user.id === 41898282) + + if (botComment) { + await github.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }) + } else { + await github.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + body: commentBody + }) + } diff --git a/.travis.yml b/.travis.yml index df0ce7d247..68b7c48660 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,3 @@ jobs: name: Spellcheck script: npm run lint:spelling env: ALLOW_FAILURE=true - - stage: Lint - name: Regression Tests Coverage Report - script: node test/util/report.js - env: ALLOW_FAILURE=true From b866f7cba69028c05ef689d11ad9fd76602ebf12 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 29 Sep 2020 12:58:23 -0400 Subject: [PATCH 08/34] Multiple Pages: fix Broken links (pull #1535) * fix: Links to example CSS files * fix: css link in mb-about.html * fix: css link in mb-academics.html * fix: css link in mb-admissions.html * fix: missing hash in anchor links --- aria-practices.html | 4 ++-- examples/combobox/grid-combo.html | 2 +- examples/menubar/mb-about.html | 2 +- examples/menubar/mb-academics.html | 2 +- examples/menubar/mb-admissions.html | 2 +- examples/radio/radio-activedescendant.html | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aria-practices.html b/aria-practices.html index aa720a716c..2f6b13c5a6 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -995,7 +995,7 @@
Tree Popup Keyboard Interaction
  • In either case, focus is visually distinct from selection so users can readily see if a value is selected or not.
  • -
  • If nodes in the tree are arranged horizontally (aria-orientation is set to horizontal): +
  • If nodes in the tree are arranged horizontally (aria-orientation is set to horizontal):
    1. Down Arrow performs as Right Arrow is described above, and vice versa.
    2. Up Arrow performs as Left Arrow is described above, and vice versa.
    3. @@ -4809,7 +4809,7 @@

      Accessible Name Guidance by Role
    4. Helps screen reader users understand the context and purpose of the search landmark.
    5. Named using aria-labelledby if a visible label is present, otherwise with aria-label.
    6. -
    7. See the Search Landmark section.
    8. +
    9. See the Search Landmark section.
    10. diff --git a/examples/combobox/grid-combo.html b/examples/combobox/grid-combo.html index 35a0f35868..63d9bb7d5a 100644 --- a/examples/combobox/grid-combo.html +++ b/examples/combobox/grid-combo.html @@ -380,7 +380,7 @@

      Grid Popup

      Javascript and CSS Source Code

      diff --git a/examples/menubar/mb-about.html b/examples/menubar/mb-about.html index 4ba20cae85..2ba7a0413d 100644 --- a/examples/menubar/mb-about.html +++ b/examples/menubar/mb-about.html @@ -2,7 +2,7 @@ Menubar Example Landing Page: About - + diff --git a/examples/menubar/mb-academics.html b/examples/menubar/mb-academics.html index 6befba7337..74bf67483e 100644 --- a/examples/menubar/mb-academics.html +++ b/examples/menubar/mb-academics.html @@ -2,7 +2,7 @@ Menubar Example Landing Page: Academics - + diff --git a/examples/menubar/mb-admissions.html b/examples/menubar/mb-admissions.html index 9c7cba1aec..8878688887 100644 --- a/examples/menubar/mb-admissions.html +++ b/examples/menubar/mb-admissions.html @@ -2,7 +2,7 @@ Menubar Example Landing Page: Admissions - + diff --git a/examples/radio/radio-activedescendant.html b/examples/radio/radio-activedescendant.html index 7f5f85a95f..03e53e0d7e 100644 --- a/examples/radio/radio-activedescendant.html +++ b/examples/radio/radio-activedescendant.html @@ -261,7 +261,7 @@

      Role, Property, State, and Tabindex Attributes

      Javascript and CSS Source Code

      From b4a99c4da3c282496f50d9dcbd1c49c2b5b9ec80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2020 09:43:19 -0700 Subject: [PATCH 09/34] Infrastructure: Bump cspell package from 4.0.56 to 4.1.0 (pull #1544) Bumps [cspell](https://github.com/streetsidesoftware/cspell) from 4.0.56 to 4.1.0. - [Release notes](https://github.com/streetsidesoftware/cspell/releases) - [Commits](https://github.com/streetsidesoftware/cspell/compare/cspell@4.0.56...cspell@4.1.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matt King --- package-lock.json | 424 +++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 234 insertions(+), 192 deletions(-) diff --git a/package-lock.json b/package-lock.json index f720a517b7..3985696cc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -630,6 +630,12 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, "autoprefixer": { "version": "9.7.6", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz", @@ -1201,18 +1207,21 @@ "dev": true }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", + "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==", "dev": true }, "comment-json": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-1.1.3.tgz", - "integrity": "sha1-aYbDMw/uDEyeAMI5jNYa+l2PI54=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-3.0.3.tgz", + "integrity": "sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg==", "dev": true, "requires": { - "json-parser": "^1.0.0" + "core-util-is": "^1.0.2", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" } }, "common-path-prefix": { @@ -1348,18 +1357,18 @@ "dev": true }, "cspell": { - "version": "4.0.56", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-4.0.56.tgz", - "integrity": "sha512-5lbuNnXOdh06+Zi1+p/iZLQDECtC/T4Kr/La3NSWdGIOLKDExA7/g0T6YHlGZnVwdNozo5Dvm0hbiC9qxmSGMQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-4.1.0.tgz", + "integrity": "sha512-Wnf8Fz7OZgzM9Z4PzR3Wuu4yGLE7y6sGoQnJH7XZ/UtZC42FnycjcTA8X9qaEKehm/s2imPEMBNuGK0NJealjA==", "dev": true, "requires": { "chalk": "^2.4.2", - "commander": "^2.20.3", - "comment-json": "^1.1.3", - "cspell-glob": "^0.1.17", - "cspell-lib": "^4.1.22", - "fs-extra": "^8.1.0", - "gensequence": "^3.0.3", + "commander": "^6.0.0", + "comment-json": "^3.0.3", + "cspell-glob": "^0.1.20", + "cspell-lib": "^4.2.0", + "fs-extra": "^9.0.1", + "gensequence": "^3.1.1", "get-stdin": "^7.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" @@ -1417,294 +1426,336 @@ } } }, + "cspell-dict-aws": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cspell-dict-aws/-/cspell-dict-aws-1.0.6.tgz", + "integrity": "sha512-EAF/XyI1RIzlAxaQSu+lxS0HxggoUO0zuDFlqfy0gb0hOGH14lcraILQ4ZExl04bAU2v7eatgoM5s4x/uFSGow==", + "dev": true, + "requires": { + "configstore": "^5.0.1" + } + }, "cspell-dict-bash": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cspell-dict-bash/-/cspell-dict-bash-1.0.3.tgz", - "integrity": "sha512-pEGuoZXhgqhpmmvdEoNY/XYDrypI37y0Z09VgKTHEblzTHo++vLyd4Z8r1SY3kJ2eQejduz4IL7ZGXqgtEp2vw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cspell-dict-bash/-/cspell-dict-bash-1.0.4.tgz", + "integrity": "sha512-/BLAhGLRsQMpLp8LdjhR7Nrt3egKIlHBg7/Lu3P+zGCxSWkhp/maObW21eAod63zJiS5WitJfL9e+E+7zxamWQ==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-companies": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/cspell-dict-companies/-/cspell-dict-companies-1.0.21.tgz", - "integrity": "sha512-vHW6pA0cLIT1qUfT6c+xV1IORrmSKuraHPJ7dwdRhWwuc6Ltc7QJWloapufxWgsYUCLllmFcv6E7kzzmue66gw==", + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/cspell-dict-companies/-/cspell-dict-companies-1.0.23.tgz", + "integrity": "sha512-MSUd2boJzgnwaSLarBF5Kiquf/mHEnv7gpOgXdpWmZ/mSmdlz1rW8/G09sCEle14YWo1/zDYX2ewi+a2SqTxmw==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-cpp": { - "version": "1.1.26", - "resolved": "https://registry.npmjs.org/cspell-dict-cpp/-/cspell-dict-cpp-1.1.26.tgz", - "integrity": "sha512-ywY7X6UzC5BC7fQhyRAwZHurl52GjwnY6D2wG57JJ/bcT5IsJOWpLAjHORtUH2AcCp6BSAKR6wxl6/bqSuKHJw==", + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/cspell-dict-cpp/-/cspell-dict-cpp-1.1.27.tgz", + "integrity": "sha512-B6BKxk/HK2PT5S0354Lf8rxdazGTABoQ3GOvgXXuxX53tUtP2jVguaBQGUt67aIMFN9hYjTC4SsYBlvWZ2Fiaw==", + "dev": true, + "requires": { + "configstore": "^5.0.1" + } + }, + "cspell-dict-cryptocurrencies": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cspell-dict-cryptocurrencies/-/cspell-dict-cryptocurrencies-1.0.3.tgz", + "integrity": "sha512-kue3B8A4MJ8jLTFHgEhJHe4pbi2R+AgrB8+JSmIaV74m7ZAu01ARKu/CBCoXm0jxYyVm2O3Ks/wBb9WXYhQIOA==", "dev": true, "requires": { "configstore": "^5.0.0" } }, "cspell-dict-django": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/cspell-dict-django/-/cspell-dict-django-1.0.15.tgz", - "integrity": "sha512-heppo6ZEGgv+cVPDLr24miG8xIn3E5SEGFBGHyNLyGqt8sHzeG3eNKhjKOJCC0hG/fq0ZECbE5q4691LvH24/Q==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/cspell-dict-django/-/cspell-dict-django-1.0.16.tgz", + "integrity": "sha512-TY31T1DQAPZ1YjYbhtIQmWpfLSA9Vu2AavdfBaCUiun+wIxWyguEfiopNSovor6StMGC9BStXipoALcXuCO7OQ==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-dotnet": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/cspell-dict-dotnet/-/cspell-dict-dotnet-1.0.14.tgz", - "integrity": "sha512-gTuh94tNAVMS4XmVCK2AsFgKp2mXBk2b8+f2GLCw2K8HY6QUHlvOJg051JJrZABRW/lAoquKZuqssSo9B1mgng==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/cspell-dict-dotnet/-/cspell-dict-dotnet-1.0.15.tgz", + "integrity": "sha512-8o6v64cM+68ggm2nVtIS+65DKKPit9uLhuV1cN3Vj8665j6FMZJAEwQY/Bjpq2J8EI9iZbkklTWOeS9jgx9wAA==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-elixir": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/cspell-dict-elixir/-/cspell-dict-elixir-1.0.13.tgz", - "integrity": "sha512-KWDO4NeV3QuMlZxSWpN0sPiFN4GE5AzlDi75eSKRvq/f1+pxgxgXQ5zLNPnDbr2EOSJBV34paZwI+7PvCiTTgA==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/cspell-dict-elixir/-/cspell-dict-elixir-1.0.14.tgz", + "integrity": "sha512-8tc7AV0TgcpklQezGksWw+O1XfnL+hwBFR400ud8k8P+iDrDLp85JiRKqAMIfXrFaS4D8LmXa35oZWaCV450hA==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-en-gb": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/cspell-dict-en-gb/-/cspell-dict-en-gb-1.1.16.tgz", - "integrity": "sha512-PBzHF40fVj+6Adm3dV3/uhkE2Ptu8W+WJ28socBDDpEfedFMwnC0rpxvAgmKJlLc0OYsn07/yzRnt9srisNrLg==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/cspell-dict-en-gb/-/cspell-dict-en-gb-1.1.17.tgz", + "integrity": "sha512-RJJiRVB1QkkYSBAxNgais07R9IPeIwTLFS+Xi+CL99YCdwQoQtp4RbBZTFYPN+wWAEupN247FK0d52FYDhVI0g==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-en_us": { - "version": "1.2.25", - "resolved": "https://registry.npmjs.org/cspell-dict-en_us/-/cspell-dict-en_us-1.2.25.tgz", - "integrity": "sha512-owr04YQAO86wMR0nSup8d7Ogkm23vIOoQsPtIMFou1OA2XLUu13Xhla/Cs+qFzopakpcblvRuMSel0RomkAo7g==", + "version": "1.2.27", + "resolved": "https://registry.npmjs.org/cspell-dict-en_us/-/cspell-dict-en_us-1.2.27.tgz", + "integrity": "sha512-wswqZLa7KV7tb9zS6oNA1s+VAehb1c76GloikexGC6A98n6EkoiSi+y9FDn9WXQPVlfrSAioLDVijPPF0Czo2Q==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-fonts": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/cspell-dict-fonts/-/cspell-dict-fonts-1.0.5.tgz", - "integrity": "sha512-R9A/MVDzqEQbwXaZhmNJ7bSzzkH5YSJ5UDr3wDRk7FXzNNcuJ4J9WRbkDjCDnoVfg0kCx0FeEp0fme+PbLTeng==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cspell-dict-fonts/-/cspell-dict-fonts-1.0.6.tgz", + "integrity": "sha512-ksuBPyXGz4NxtlICul/ELLVYDT4s3SQIBwiMlhlL74kvaEjhJUdgfBs5ZBzkT9XZrUTNkMe4FGXsyZPTuPtw2Q==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-fullstack": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/cspell-dict-fullstack/-/cspell-dict-fullstack-1.0.22.tgz", - "integrity": "sha512-k8Op1ltkgKnMTTo/kgkywE0htwi+3EtYrPPWk+mD9o3IFgC6yLKA89Tkrd0kEEPR3qJvC4gQJmGJns6Y25v0Zg==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/cspell-dict-fullstack/-/cspell-dict-fullstack-1.0.24.tgz", + "integrity": "sha512-TfcAsYXBpJBTK8IamP4uUAF3+UAnqxZ5fw1jqDfWqy0TaEiePahL92ibrqw5wKqJmis0O169VdP5iETznGT+Vg==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-golang": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/cspell-dict-golang/-/cspell-dict-golang-1.1.14.tgz", - "integrity": "sha512-V9TQQjoTgdLTpLNczEjoF+BO+CkdmuZlD6J71SCT8sczSP0FLz4QkL1MpqiL0lhdnbtASsjs+oCF53Y+dWdh9g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/cspell-dict-golang/-/cspell-dict-golang-1.1.15.tgz", + "integrity": "sha512-Yc1XhYGcKLR7Xp29XS5Ypd3eeqVQGE79QfyuR03OXEkZ0cVvleH1sPO5kOoKlcnQjEKwhPgkWjfb/SNrq0D/WA==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-haskell": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cspell-dict-haskell/-/cspell-dict-haskell-1.0.4.tgz", - "integrity": "sha512-Wy5EE446icPbsi8bLqSCOtxS5Z6QDLGNBvz6Nh+yvuLf7Nb8mU6NQmfSYH/yMfJoVGa5bpcmv8pQtJV4I2E5Tg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/cspell-dict-haskell/-/cspell-dict-haskell-1.0.5.tgz", + "integrity": "sha512-imNCu1qn8yfn0/uMaZep9HJHuxUlTNTrY3M+TD7dsi6NtXcUE8f7pE+BIuO8f1xfg+m991B8ZMbRAAN3+E0jXw==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-html-symbol-entities": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/cspell-dict-html-symbol-entities/-/cspell-dict-html-symbol-entities-1.0.13.tgz", - "integrity": "sha512-u8BARt4r5rdUee7Yw6ejsD69WLib9l+pyBr4UUIZovhCUccddm2LkS9GDJUqWtCf/frZpoTnmpuW/NPWVVG6pQ==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/cspell-dict-html-symbol-entities/-/cspell-dict-html-symbol-entities-1.0.14.tgz", + "integrity": "sha512-hLWjTcLJmY+6DTNIsSC1Uuq54uIxYSianJFm1fEvxzgUYFcPts/HLLNyFD+OuWMBX4KHd/ihoyAFwiiLabjC8Q==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-java": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/cspell-dict-java/-/cspell-dict-java-1.0.12.tgz", - "integrity": "sha512-9pg5IrCEZGlWLgv8qGjxzzca19egfBYrbnuiWhJNLbBGBOTWrwYjFqbLQtMJReXUtWikWLY0KCzRZlCGusr7bw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/cspell-dict-java/-/cspell-dict-java-1.0.13.tgz", + "integrity": "sha512-PjtqsqyO00q/L/g3F5tcW8VG5Z66MIddM+YMMS+O40giSGJ23e4Ts++SBnP8IYmDfi/KDElWFwLJmb+1RZgAkg==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-latex": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/cspell-dict-latex/-/cspell-dict-latex-1.0.13.tgz", - "integrity": "sha512-UZqGJQ82mkzseqdF7kWXIrA07VD91W7rWx16DCThDBMohOsFdvCymUUgr0pM90FuqmldSiD+Gi1FayDSyPdNtQ==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/cspell-dict-latex/-/cspell-dict-latex-1.0.14.tgz", + "integrity": "sha512-VSUzrIyA+zc08T+3q46AAq5tdsF8PsRZaBLFxQZwZ1n3dAhyWzkiQ07iwawuu6DkAeRbNmGaYg9Rd9vMAd9tUw==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-lorem-ipsum": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/cspell-dict-lorem-ipsum/-/cspell-dict-lorem-ipsum-1.0.10.tgz", - "integrity": "sha512-UlboQ3xH+D3l+hemLO4J5yz8EM60SH91f1dJIy2s94AeePZXtwYh1hTFM5dEsXI2CAQkfTu3ZdPWflLsInPfrA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/cspell-dict-lorem-ipsum/-/cspell-dict-lorem-ipsum-1.0.12.tgz", + "integrity": "sha512-CAJJH8Fwm+itcRMDUCWdcL43120b91umWwkotvkxjWmlxrTl57aIveC/RGksQ6lSaymPNL7jg9K68YoKF7DMRw==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" + } + }, + "cspell-dict-lua": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/cspell-dict-lua/-/cspell-dict-lua-1.0.9.tgz", + "integrity": "sha512-28o0KHYuD7wbjgG57SnlJqp2nF2r3diqx4BuYcVGli0+i12oCPu6Ii6n6g3U4yXSLfsch21GjfSbPMa8FeOepQ==", + "dev": true, + "requires": { + "configstore": "^5.0.1" } }, "cspell-dict-php": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/cspell-dict-php/-/cspell-dict-php-1.0.13.tgz", - "integrity": "sha512-RP5XST+hWEqWxlLISS3sXxsQa2YXOWx8X5LcxQHvEGdb1hMNypXxw9V53th7S+hfUTPKJrbUIzckYZp4j8TS4A==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/cspell-dict-php/-/cspell-dict-php-1.0.14.tgz", + "integrity": "sha512-MD+86VH263sl4t2OJd0/2aHuJcPVNMJTm8bszk+MOB29LnmYdTbu2Fu5miIZ1l+zVpAZr0wfYVZWsYtuSkFGUQ==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-powershell": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cspell-dict-powershell/-/cspell-dict-powershell-1.0.6.tgz", - "integrity": "sha512-rwxt/fG3Nr7tQaV7e38ilz8qWfXrf5Ie+MQC6Mw/ddjT4wLOkGvruUqtJA/USoDE9PFG12KoarFsWlVXv/nwPA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cspell-dict-powershell/-/cspell-dict-powershell-1.0.7.tgz", + "integrity": "sha512-Ay+lFRZP6pvSMBkkJ9PPjuqgfqufeEgohPjZ34/yh6xXODkmopsf7sgUkirdjcFryosilnDze0Mwii1o+iEwJA==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-python": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/cspell-dict-python/-/cspell-dict-python-1.0.20.tgz", - "integrity": "sha512-BiV8LnH9YNxvkUbVwTyDpZhOuRjPr8cE+nxpuPDbCHmVJmlLsDlg8MXTcJH8I+OFjoz6YdBX6yqK1bi55Aioow==", + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/cspell-dict-python/-/cspell-dict-python-1.0.21.tgz", + "integrity": "sha512-RQYvsxD40YGr6I8vkuTiWlXy4ccVT5F3+kIZwikqk/NeN7/1SuiRVBpAzRkzh+X+IW3RlxBfs2TqcIjWVp6Tjg==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-ruby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cspell-dict-ruby/-/cspell-dict-ruby-1.0.3.tgz", - "integrity": "sha512-uFxUyGj9SRASfnd75lcpkoNvMYHNWmqkFmS9ZruL61M1RmFx9eekuEY74nK11qsb/E4o6yPtGAQH4SrotF9SwQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/cspell-dict-ruby/-/cspell-dict-ruby-1.0.5.tgz", + "integrity": "sha512-RohA/GEQTtyVZMWbhbdQ0R+u4JpNdb70KVMRAE10HxIqV7zNqBAvpp6shP1GaBZZ+Fdm+I+HDJyG/7OjMwJaTA==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-rust": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/cspell-dict-rust/-/cspell-dict-rust-1.0.12.tgz", - "integrity": "sha512-bMt70/aQL2OcadZRtWfPIF/mHWX9JNOGq92UUU2ka+9C3OPBP/TuyYiHhUWt67y/CoIyEQ7/5uAtjX8paLf14w==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/cspell-dict-rust/-/cspell-dict-rust-1.0.13.tgz", + "integrity": "sha512-Ib8CcgSB/bUYyM51te2xkkasYHgtlhhaE0CLRkBKQBpKs+OjSqk7Y+wsyPjJR/C8m29k7QFnPGA3ueq5UzMUYw==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-scala": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/cspell-dict-scala/-/cspell-dict-scala-1.0.11.tgz", - "integrity": "sha512-bmAQjapvcceJaiwGTkBd9n2L9GaqpmFDKe5S19WQDsWqjFiDwQ+r47td3TU7yWjOLPqp72h9X/XGzDJFvQEPcg==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/cspell-dict-scala/-/cspell-dict-scala-1.0.12.tgz", + "integrity": "sha512-sFqTlLNI1z2NnvTusJcdP2xnIk4X+rdg6Df5ifZ/cEXvf0U45UofdTwgZ39ISEgQ12d9bPQtPZ0+Td5w/FDkig==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-software-terms": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cspell-dict-software-terms/-/cspell-dict-software-terms-1.0.7.tgz", - "integrity": "sha512-Fh8NmDqY+GZRrJJuFUNoIDbR9WoP9mte+nVVGK5u8vurNInOG/MgRL0O/dhDfTmrMlSyAMhlUWm+852sXietEA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/cspell-dict-software-terms/-/cspell-dict-software-terms-1.0.12.tgz", + "integrity": "sha512-2jPDJoGRpbhuB4MGeau/zdy7fzRqIKSy0vMQPGVs7Z7wLZ7QsqJw827veUBL2ZHPelBllkeBcIN5hhuSuAfNdg==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-dict-typescript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cspell-dict-typescript/-/cspell-dict-typescript-1.0.4.tgz", - "integrity": "sha512-cniGSmTohYriEgGJ0PgcQP2GCGP+PH/0WZ2N7BTTemQr/mHTU6bKWy8DVK63YEtYPEmhZv+G2xPBgBD41QQypQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cspell-dict-typescript/-/cspell-dict-typescript-1.0.6.tgz", + "integrity": "sha512-U2gA19Rqpoy/UAFk3ENgncQNCB3+55Upz3PrZ9YCMTuTkGWnm+e/TJZMSRWzpQbGafc2i3ZIeBQQ8CzAqL5VQg==", "dev": true, "requires": { - "configstore": "^5.0.0" + "configstore": "^5.0.1" } }, "cspell-glob": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-0.1.17.tgz", - "integrity": "sha512-gAiKakWJbHay6cobcJnX1+XhNCFYqR7CJM5GPiEpRZ5RFXYR46fYbkVwTdg3sqbFLErJtghQj/0s5Xa0q9NJpQ==", + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-0.1.20.tgz", + "integrity": "sha512-pXKdl6m7WjGqQNxv4T1II+hxDvzqZHHcSjLZL9kgszlimpIriNiTN7PRg0oFbda0fyIWJ9CuLo7AbNuGnJbZQg==", "dev": true, "requires": { "micromatch": "^4.0.2" } }, "cspell-io": { - "version": "4.0.20", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-4.0.20.tgz", - "integrity": "sha512-fomz1P308XgyyxaOEKdNbh82Ac4AKaz26p4JszV7YkJrGDsXMoByTQjVqdDloNN8FchogSEpLPeQoIg648exBA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-4.1.0.tgz", + "integrity": "sha512-FOkS/VFTJPmipB2ObG94VdoBqNuH0hqZpyMPy7HVuAiY18hx0L13F43ZxpLHDEKfMVtRQfB4HCYAffm6yMUzRw==", "dev": true, "requires": { - "iconv-lite": "^0.4.24", + "iconv-lite": "^0.6.2", "iterable-to-stream": "^1.0.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, "cspell-lib": { - "version": "4.1.22", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-4.1.22.tgz", - "integrity": "sha512-O+BcCLYce9mKKgzp49cE4hHvIDb3w9GJ0FcoFrERNfxKt0ciPLmNdflnxjEo/lXr9s+aXmjRVFm+BI6sDpDZvw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-4.2.0.tgz", + "integrity": "sha512-JmH6c5/lRUxZ5noEUv0rSYtfJAcx6qrrFF/1cuj4uesABIS2mcg/HcAerByzTRvl2AU5kvQu3erEF2cttbrZQQ==", "dev": true, "requires": { - "comment-json": "^1.1.3", + "comment-json": "^3.0.3", "configstore": "^5.0.1", - "cspell-dict-bash": "^1.0.3", - "cspell-dict-companies": "^1.0.20", - "cspell-dict-cpp": "^1.1.26", - "cspell-dict-django": "^1.0.15", - "cspell-dict-dotnet": "^1.0.14", - "cspell-dict-elixir": "^1.0.13", - "cspell-dict-en-gb": "^1.1.16", - "cspell-dict-en_us": "^1.2.25", - "cspell-dict-fonts": "^1.0.5", - "cspell-dict-fullstack": "^1.0.22", - "cspell-dict-golang": "^1.1.14", - "cspell-dict-haskell": "^1.0.4", - "cspell-dict-html-symbol-entities": "^1.0.13", - "cspell-dict-java": "^1.0.12", - "cspell-dict-latex": "^1.0.13", - "cspell-dict-lorem-ipsum": "^1.0.10", - "cspell-dict-php": "^1.0.13", - "cspell-dict-powershell": "^1.0.6", - "cspell-dict-python": "^1.0.20", - "cspell-dict-ruby": "^1.0.3", - "cspell-dict-rust": "^1.0.12", - "cspell-dict-scala": "^1.0.11", - "cspell-dict-software-terms": "^1.0.6", - "cspell-dict-typescript": "^1.0.3", - "cspell-io": "^4.0.20", - "cspell-trie-lib": "^4.1.8", - "cspell-util-bundle": "^4.0.10", - "fs-extra": "^8.1.0", - "gensequence": "^3.0.3", - "vscode-uri": "^2.1.1" + "cspell-dict-aws": "^1.0.6", + "cspell-dict-bash": "^1.0.4", + "cspell-dict-companies": "^1.0.23", + "cspell-dict-cpp": "^1.1.27", + "cspell-dict-cryptocurrencies": "^1.0.3", + "cspell-dict-django": "^1.0.16", + "cspell-dict-dotnet": "^1.0.15", + "cspell-dict-elixir": "^1.0.14", + "cspell-dict-en-gb": "^1.1.17", + "cspell-dict-en_us": "^1.2.27", + "cspell-dict-fonts": "^1.0.6", + "cspell-dict-fullstack": "^1.0.24", + "cspell-dict-golang": "^1.1.15", + "cspell-dict-haskell": "^1.0.5", + "cspell-dict-html-symbol-entities": "^1.0.14", + "cspell-dict-java": "^1.0.13", + "cspell-dict-latex": "^1.0.14", + "cspell-dict-lorem-ipsum": "^1.0.12", + "cspell-dict-lua": "^1.0.9", + "cspell-dict-php": "^1.0.14", + "cspell-dict-powershell": "^1.0.7", + "cspell-dict-python": "^1.0.21", + "cspell-dict-ruby": "^1.0.4", + "cspell-dict-rust": "^1.0.13", + "cspell-dict-scala": "^1.0.12", + "cspell-dict-software-terms": "^1.0.11", + "cspell-dict-typescript": "^1.0.6", + "cspell-io": "^4.1.0", + "cspell-trie-lib": "^4.2.0", + "cspell-util-bundle": "^4.1.0", + "fs-extra": "^9.0.1", + "gensequence": "^3.1.1", + "minimatch": "^3.0.4", + "vscode-uri": "^2.1.2" } }, "cspell-trie-lib": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-4.1.8.tgz", - "integrity": "sha512-G0Jpybwxyl7rG3c4tzrROEVmiKAsyIjaDdnGxkzOFkl4tjcZeCh7GIVrqLyyk3VWslrWMVvmQi1/eLDccagepw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-4.2.0.tgz", + "integrity": "sha512-yovSTcupq1T/PWr/oVjV3rAB0r80f/r62kkVk3dluICPUKtCKIjtmlJqqRR12X6gEOxWKromaT4No3WF+c7W6Q==", "dev": true, "requires": { - "gensequence": "^3.0.3" + "gensequence": "^3.1.1" } }, "cspell-util-bundle": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/cspell-util-bundle/-/cspell-util-bundle-4.0.10.tgz", - "integrity": "sha512-XpBNIwVfJz9OVLR/ztW4BG75UbqmlBh31R5CpmNFxNF7QeiQwIrU8QMYzjtSnIwmOe6t0Hsx+X1UC2KUqMhRtA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cspell-util-bundle/-/cspell-util-bundle-4.1.0.tgz", + "integrity": "sha512-mxVrlCoUIyE6yQIHgsvJTDq6N0yH4TOnL0eHS/7MpWu+n7kQVgR7Kndn3RWln3tZTXzvFMp9yNLSE06Ema5jQA==", "dev": true }, "css-select": { @@ -2491,14 +2542,15 @@ "dev": true }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { + "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" } }, "fs-minipass": { @@ -2807,6 +2859,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true + }, "has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", @@ -3300,23 +3358,6 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, - "json-parser": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/json-parser/-/json-parser-1.1.5.tgz", - "integrity": "sha1-5i7FJh0aal/CDoEqMgdAxtkAVnc=", - "dev": true, - "requires": { - "esprima": "^2.7.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - } - } - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3339,12 +3380,13 @@ } }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" } }, "jszip": { @@ -6481,9 +6523,9 @@ } }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true }, "unzip-response": { @@ -6601,9 +6643,9 @@ "dev": true }, "vscode-uri": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", - "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==", "dev": true }, "wcwidth": { diff --git a/package.json b/package.json index c554d24d26..2b4b4d0064 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "ava": "^3.7.1", "cheerio": "^1.0.0-rc.2", - "cspell": "^4.0.56", + "cspell": "^4.1.0", "eslint": "^6.8.0", "geckodriver": "^1.19.1", "glob": "^7.1.6", From 9d19d9466a82cc1dcf9ae258c034ae4d8e9fc9bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2020 10:27:26 -0700 Subject: [PATCH 10/34] Infrastructure: Bump actions/github-script package from 0.9.0 to v3 (pull #1547) Bumps [actions/github-script](https://github.com/actions/github-script) from 0.9.0 to v3. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/0.9.0...626af12fe9a53dc2972b48385e7fe7dec79145c9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matt King --- .github/workflows/coverage-report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage-report.yml b/.github/workflows/coverage-report.yml index 98a2b371de..35955505f5 100644 --- a/.github/workflows/coverage-report.yml +++ b/.github/workflows/coverage-report.yml @@ -24,7 +24,7 @@ jobs: node test/util/report.js >> coverage.log || true - name: Comment on PR - uses: actions/github-script@0.9.0 + uses: actions/github-script@v3 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | From 7c3b3cc838f286dd8b7e0797cb9e71bfef88f37d Mon Sep 17 00:00:00 2001 From: Valerie Young Date: Wed, 30 Sep 2020 10:37:44 -0700 Subject: [PATCH 11/34] Fix links to Carousel Example with Buttons for Slide Control (pull #1530) * Fix carousel-prev-next link * Update link to carousel-2 Co-authored-by: Matt King --- aria-practices.html | 2 +- examples/carousel/carousel-2-tablist.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aria-practices.html b/aria-practices.html index 2f6b13c5a6..9771a007dd 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -510,7 +510,7 @@

      Carousel (Slide Show or Image Rotator)

      Examples

      diff --git a/examples/carousel/carousel-2-tablist.html b/examples/carousel/carousel-2-tablist.html index c41649f6a2..286408c446 100644 --- a/examples/carousel/carousel-2-tablist.html +++ b/examples/carousel/carousel-2-tablist.html @@ -39,7 +39,7 @@

      Auto-Rotating Image Carousel with Tabs for Slide Control Example

      Similar examples include:

      From 908af410a3e8406d870133f2cc4a0deb97e2e63f Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 30 Sep 2020 15:07:03 -0400 Subject: [PATCH 12/34] Infrastructure: Convert spellcheck to GitHub Action making it required (pull #1520) --- .github/workflows/cspell-problem-matcher.json | 16 ++++++++ .github/workflows/spelling.yml | 39 +++++++++++++++++++ .travis.yml | 7 ---- 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/cspell-problem-matcher.json create mode 100644 .github/workflows/spelling.yml diff --git a/.github/workflows/cspell-problem-matcher.json b/.github/workflows/cspell-problem-matcher.json new file mode 100644 index 0000000000..c5cc711b11 --- /dev/null +++ b/.github/workflows/cspell-problem-matcher.json @@ -0,0 +1,16 @@ +{ + "problemMatcher": [ + { + "owner": "cspell", + "pattern": [ + { + "regexp": "^(.*):(\\d+):(\\d+)\\s+\\-\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml new file mode 100644 index 0000000000..2c00ee6550 --- /dev/null +++ b/.github/workflows/spelling.yml @@ -0,0 +1,39 @@ +name: Spellcheck + +on: + push: + paths: + - "package*.json" + - "cspell.json" + - "aria-practices.html" + - "examples/**/*.html" + - ".github/workflows/cspell-problem-matcher.json" + - ".github/workflows/spelling.yml" + + pull_request: + paths: + - "package*.json" + - "cspell.json" + - "aria-practices.html" + - "examples/**/*.html" + - ".github/workflows/cspell-problem-matcher.json" + - ".github/workflows/spelling.yml" + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2.1.1 + + - name: Install npm dependencies + run: npm ci + + - name: cSpell + run: | + echo "::add-matcher::.github/workflows/cspell-problem-matcher.json" + npm run lint:spelling diff --git a/.travis.yml b/.travis.yml index 68b7c48660..a4b32dd7da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,8 @@ stages: - Lint jobs: - fast_finish: true - allow_failures: - - env: ALLOW_FAILURE=true include: - stage: Lint name: HTML Linting script: npm run vnu-jar - - stage: Lint - name: Spellcheck - script: npm run lint:spelling - env: ALLOW_FAILURE=true From 9489ed5a6dd25ff1c3e069ee90dfdf3ada2fb411 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2020 13:05:05 -0700 Subject: [PATCH 13/34] Infrastructure: Bump stylelint package from 13.3.2 to 13.7.2 (pull #1539) Bumps [stylelint](https://github.com/stylelint/stylelint) from 13.3.2 to 13.7.2. - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/master/CHANGELOG.md) - [Commits](https://github.com/stylelint/stylelint/compare/13.3.2...13.7.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 1009 +++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 608 insertions(+), 403 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3985696cc9..e3fcfca9b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,46 +14,121 @@ } }, "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "@babel/generator": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", - "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", "dev": true, "requires": { - "@babel/types": "^7.9.5", + "@babel/types": "^7.11.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" }, "dependencies": { @@ -66,96 +141,96 @@ } }, "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.11.0" } }, "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.11.0" } }, "@babel/helper-validator-identifier": { @@ -165,14 +240,14 @@ "dev": true }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/highlight": { @@ -239,9 +314,9 @@ } }, "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/runtime": { @@ -254,50 +329,212 @@ } }, "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/traverse": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", - "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.5", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.5", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" }, "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "@babel/types": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", - "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + } } }, "@concordance/react": { @@ -359,9 +596,9 @@ "dev": true }, "@stylelint/postcss-css-in-js": { - "version": "0.37.1", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz", - "integrity": "sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw==", + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", + "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==", "dev": true, "requires": { "@babel/core": ">=7.9.0" @@ -637,70 +874,18 @@ "dev": true }, "autoprefixer": { - "version": "9.7.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz", - "integrity": "sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==", + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", "dev": true, "requires": { - "browserslist": "^4.11.1", - "caniuse-lite": "^1.0.30001039", - "chalk": "^2.4.2", + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.27", - "postcss-value-parser": "^4.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" } }, "ava": { @@ -894,15 +1079,15 @@ } }, "browserslist": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz", - "integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==", + "version": "4.14.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", + "integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001038", - "electron-to-chromium": "^1.3.390", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" + "caniuse-lite": "^1.0.30001135", + "electron-to-chromium": "^1.3.571", + "escalade": "^3.1.0", + "node-releases": "^1.1.61" } }, "buffer-from": { @@ -967,9 +1152,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001040", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz", - "integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==", + "version": "1.0.30001140", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001140.tgz", + "integrity": "sha512-xFtvBtfGrpjTOxTpjP5F2LmN04/ZGfYV8EQzUIC/RmKpdrmzJrjqlJ4ho7sGuAMPko2/Jl08h7x9uObCfBFaAA==", "dev": true }, "capture-stack-trace": { @@ -1206,6 +1391,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, "commander": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", @@ -2028,9 +2219,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.403", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.403.tgz", - "integrity": "sha512-JaoxV4RzdBAZOnsF4dAlZ2ijJW72MbqO5lNfOBHUWiBQl3Rwe+mk2RCUMrRI3rSClLJ8HSNQNqcry12H+0ZjFw==", + "version": "1.3.576", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.576.tgz", + "integrity": "sha512-uSEI0XZ//5ic+0NdOqlxp0liCD44ck20OAGyLMSymIWTEAtHKVJi6JM18acOnRgUgX7Q65QqnI+sNncNvIy8ew==", "dev": true }, "elegant-spinner": { @@ -2096,6 +2287,12 @@ "es6-promise": "^4.0.3" } }, + "escalade": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==", + "dev": true + }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -2458,6 +2655,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, "fastq": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz", @@ -2777,9 +2980,9 @@ } }, "globby": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", - "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -3358,6 +3561,12 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3443,9 +3652,9 @@ "dev": true }, "known-css-properties": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.18.0.tgz", - "integrity": "sha512-69AgJ1rQa7VvUsd2kpvVq+VeObDuo3zrj0CzM5Slmf6yduQFAI2kXPDQJR2IE/u6MSAUOJrwSzjg5vlz8qcMiw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.19.0.tgz", + "integrity": "sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA==", "dev": true }, "latest-version": { @@ -3457,12 +3666,6 @@ "package-json": "^6.3.0" } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -4086,29 +4289,39 @@ } }, "meow": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.0.tgz", - "integrity": "sha512-iIAoeI01v6pmSfObAAWFoITAA4GgiT45m4SmJgoxtZfvI0fyZwhV4d0lTwiUXvAKIPlma05Feb2Xngl52Mj5Cg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", "dev": true, "requires": { "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.1.1", + "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.0.0", - "minimist-options": "^4.0.1", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.0", + "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", - "type-fest": "^0.8.1", - "yargs-parser": "^18.1.1" + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" }, "dependencies": { "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -4147,9 +4360,9 @@ "dev": true }, "min-indent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", - "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, "minimatch": { @@ -4168,13 +4381,14 @@ "dev": true }, "minimist-options": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.0.2.tgz", - "integrity": "sha512-seq4hpWkYSUh1y7NXxzucwAN9yVlBc3Upgdjz8vLCP97jG8kaOmzYrVH/m7tQ1NYD1wdtZbSLfdy4zFmRWuc/w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "requires": { "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" }, "dependencies": { "arrify": { @@ -4238,9 +4452,9 @@ "dev": true }, "node-releases": { - "version": "1.1.53", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz", - "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==", + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", "dev": true }, "node-status-codes": { @@ -5013,66 +5227,6 @@ "find-up": "^4.0.0" } }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, "please-upgrade-node": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", @@ -5092,9 +5246,9 @@ } }, "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -5189,79 +5343,6 @@ "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", "dev": true }, - "postcss-reporter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", - "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "lodash": "^4.17.11", - "log-symbols": "^2.2.0", - "postcss": "^7.0.7" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -5288,23 +5369,24 @@ } }, "postcss-scss": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.0.0.tgz", - "integrity": "sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", "dev": true, "requires": { - "postcss": "^7.0.0" + "postcss": "^7.0.6" } }, "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", "dev": true, "requires": { "cssesc": "^3.0.0", "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" } }, "postcss-syntax": { @@ -5314,9 +5396,9 @@ "dev": true }, "postcss-value-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", - "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, "prelude-ls": { @@ -5543,9 +5625,9 @@ } }, "remark": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz", - "integrity": "sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz", + "integrity": "sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw==", "dev": true, "requires": { "remark-parse": "^8.0.0", @@ -5554,9 +5636,9 @@ } }, "remark-parse": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.0.tgz", - "integrity": "sha512-Ck14G1Ns/GEPXhSw6m1Uv28kMtVk63e59NyL+QlhBBwBdIUXROM6MPfBehPhW6TW2d73batMdZsKwuxl5i3tEA==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -5578,9 +5660,9 @@ } }, "remark-stringify": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.0.0.tgz", - "integrity": "sha512-cABVYVloFH+2ZI5bdqzoOmemcz/ZuhQSH6W6ZNYnLojAUUn3xtX7u+6BpnYp35qHoGr2NFBsERV14t4vCIeW8w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.1.tgz", + "integrity": "sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -5963,9 +6045,9 @@ } }, "stringify-entities": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.0.tgz", - "integrity": "sha512-h7NJJIssprqlyjHT2eQt2W1F+MCcNmwPGlKb0bWEdET/3N44QN3QbUF/ueKCgAssyKRZ3Br9rQ7FcXjHr0qLHw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz", + "integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==", "dev": true, "requires": { "character-entities-html4": "^1.0.0", @@ -6037,48 +6119,48 @@ "dev": true }, "stylelint": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.3.2.tgz", - "integrity": "sha512-kpO3/Gz2ZY40EWUwFYYkgpzhf8ZDUyKpcui5+pS0XKJBj/EMYmZpOJoL8IFAz2yApYeg91NVy5yAjE39hDzWvQ==", + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.7.2.tgz", + "integrity": "sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg==", "dev": true, "requires": { - "@stylelint/postcss-css-in-js": "^0.37.1", + "@stylelint/postcss-css-in-js": "^0.37.2", "@stylelint/postcss-markdown": "^0.36.1", - "autoprefixer": "^9.7.6", + "autoprefixer": "^9.8.6", "balanced-match": "^1.0.0", - "chalk": "^4.0.0", - "cosmiconfig": "^6.0.0", + "chalk": "^4.1.0", + "cosmiconfig": "^7.0.0", "debug": "^4.1.1", "execall": "^2.0.0", + "fast-glob": "^3.2.4", + "fastest-levenshtein": "^1.0.12", "file-entry-cache": "^5.0.1", - "get-stdin": "^7.0.0", + "get-stdin": "^8.0.0", "global-modules": "^2.0.0", - "globby": "^11.0.0", + "globby": "^11.0.1", "globjoin": "^0.1.4", "html-tags": "^3.1.0", - "ignore": "^5.1.4", + "ignore": "^5.1.8", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.18.0", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "log-symbols": "^3.0.0", + "known-css-properties": "^0.19.0", + "lodash": "^4.17.20", + "log-symbols": "^4.0.0", "mathml-tag-names": "^2.1.3", - "meow": "^6.1.0", + "meow": "^7.1.1", "micromatch": "^4.0.2", "normalize-selector": "^0.2.0", - "postcss": "^7.0.27", + "postcss": "^7.0.32", "postcss-html": "^0.36.0", "postcss-less": "^3.1.4", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^6.0.1", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^4.0.2", "postcss-sass": "^0.4.4", - "postcss-scss": "^2.0.0", + "postcss-scss": "^2.1.1", "postcss-selector-parser": "^6.0.2", "postcss-syntax": "^0.36.2", - "postcss-value-parser": "^4.0.3", + "postcss-value-parser": "^4.1.0", "resolve-from": "^5.0.0", "slash": "^3.0.0", "specificity": "^0.4.1", @@ -6087,16 +6169,139 @@ "style-search": "^0.1.0", "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^5.4.6", - "v8-compile-cache": "^2.1.0", + "table": "^6.0.1", + "v8-compile-cache": "^2.1.1", "write-file-atomic": "^3.0.3" }, "dependencies": { + "ajv": { + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, "import-lazy": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "table": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.3.tgz", + "integrity": "sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + } + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true } } }, @@ -6432,9 +6637,9 @@ } }, "unified": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz", - "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", "dev": true, "requires": { "bail": "^1.0.0", @@ -6502,9 +6707,9 @@ } }, "unist-util-visit": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", - "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -6513,9 +6718,9 @@ } }, "unist-util-visit-parents": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz", - "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz", + "integrity": "sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -6608,9 +6813,9 @@ } }, "vfile": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.0.tgz", - "integrity": "sha512-BaTPalregj++64xbGK6uIlsurN3BCRNM/P2Pg8HezlGzKd1O9PrwIac6bd9Pdx2uTb0QHoioZ+rXKolbVXEgJg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz", + "integrity": "sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -6621,9 +6826,9 @@ } }, "vfile-location": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", - "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.1.0.tgz", + "integrity": "sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g==", "dev": true }, "vfile-message": { diff --git a/package.json b/package.json index 2b4b4d0064..a9b31df3ce 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "npm-merge-driver": "^2.3.6", "run-node": "^2.0.0", "selenium-webdriver": "^4.0.0-alpha.7", - "stylelint": "^13.3.2", + "stylelint": "^13.7.2", "stylelint-config-standard": "^20.0.0", "vnu-jar": "^20.3.16" }, From 04c8d651787ba0869c54bffe19b157b5fa9271c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2020 14:12:47 -0700 Subject: [PATCH 14/34] Infrastructure: Bump eslint package from 6.8.0 to 7.10.0 (pull #1541) Bumps [eslint](https://github.com/eslint/eslint) from 6.8.0 to 7.10.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v6.8.0...v7.10.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 449 +++++++++++++++++----------------------------- package.json | 2 +- 2 files changed, 169 insertions(+), 282 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3fcfca9b1..168eae1418 100644 --- a/package-lock.json +++ b/package-lock.json @@ -554,6 +554,38 @@ } } }, + "@eslint/eslintrc": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", + "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -676,15 +708,15 @@ "dev": true }, "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true }, "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, "acorn-walk": { @@ -719,9 +751,9 @@ } }, "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -779,22 +811,11 @@ } } }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true }, "ansi-regex": { "version": "5.0.0", @@ -1203,12 +1224,6 @@ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "cheerio": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", @@ -1306,12 +1321,6 @@ "string-width": "^4.2.0" } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1529,16 +1538,25 @@ } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "crypto-random-string": { @@ -2251,6 +2269,15 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -2306,22 +2333,24 @@ "dev": true }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz", + "integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.1.3", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^1.3.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", @@ -2330,73 +2359,24 @@ "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", + "levn": "^0.4.1", + "lodash": "^4.17.19", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -2404,71 +2384,53 @@ "dev": true }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", "dev": true, "requires": { - "acorn": "^7.1.1", + "acorn": "^7.4.0", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^1.3.0" } }, "esprima": { @@ -2478,29 +2440,37 @@ "dev": true }, "esquery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", - "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^5.0.0" + "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", - "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -2606,17 +2576,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, "fast-deep-equal": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", @@ -3151,15 +3110,6 @@ "which-pm-runs": "^1.0.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, "ignore": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", @@ -3252,39 +3202,6 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, "irregular-plurals": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.2.0.tgz", @@ -3667,13 +3584,13 @@ } }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, "lie": { @@ -4445,12 +4362,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "node-releases": { "version": "1.1.61", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", @@ -4943,17 +4854,17 @@ "dev": true }, "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, "ora": { @@ -5128,9 +5039,9 @@ "dev": true }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { @@ -5402,9 +5313,9 @@ "dev": true }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prepend-http": { @@ -5601,9 +5512,9 @@ "dev": true }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, "registry-auth-token": { @@ -5763,15 +5674,6 @@ "glob": "^7.1.3" } }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, "run-node": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/run-node/-/run-node-2.0.0.tgz", @@ -5890,18 +5792,18 @@ "dev": true }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "signal-exit": { @@ -6518,12 +6420,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, "time-zone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", @@ -6536,15 +6432,6 @@ "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", "dev": true }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6603,12 +6490,12 @@ "dev": true }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-fest": { @@ -6797,9 +6684,9 @@ "dev": true }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, "validate-npm-package-license": { diff --git a/package.json b/package.json index a9b31df3ce..6215c016e5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "ava": "^3.7.1", "cheerio": "^1.0.0-rc.2", "cspell": "^4.1.0", - "eslint": "^6.8.0", + "eslint": "^7.10.0", "geckodriver": "^1.19.1", "glob": "^7.1.6", "husky": "^4.2.5", From cc5f061a4d68016c866841becde755fe0094d3de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2020 14:46:55 -0700 Subject: [PATCH 15/34] Infrastructure: Bump lint-staged from 10.1.3 to 10.4.0 (pul #1540) Bumps [lint-staged](https://github.com/okonet/lint-staged) from 10.1.3 to 10.4.0. - [Release notes](https://github.com/okonet/lint-staged/releases) - [Commits](https://github.com/okonet/lint-staged/compare/v10.1.3...v10.4.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 580 +++++++++------------------------------------- package.json | 2 +- 2 files changed, 105 insertions(+), 477 deletions(-) diff --git a/package-lock.json b/package-lock.json index 168eae1418..fe1c9ba080 100644 --- a/package-lock.json +++ b/package-lock.json @@ -612,15 +612,6 @@ "fastq": "^1.6.0" } }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", - "dev": true, - "requires": { - "any-observable": "^0.3.0" - } - }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -817,6 +808,23 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -833,12 +841,6 @@ "color-convert": "^2.0.1" } }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -1373,12 +1375,6 @@ "convert-to-spaces": "^1.0.1" } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "collapse-white-space": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", @@ -2000,12 +1996,6 @@ "array-find-index": "^1.0.1" } }, - "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true - }, "date-time": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", @@ -2242,12 +2232,6 @@ "integrity": "sha512-uSEI0XZ//5ic+0NdOqlxp0liCD44ck20OAGyLMSymIWTEAtHKVJi6JM18acOnRgUgX7Q65QqnI+sNncNvIy8ew==", "dev": true }, - "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", - "dev": true - }, "emittery": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.1.tgz", @@ -2486,9 +2470,9 @@ "dev": true }, "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -2498,26 +2482,14 @@ "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" }, "dependencies": { - "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -2528,36 +2500,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -2998,23 +2940,6 @@ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3333,15 +3258,6 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, - "is-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", - "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", - "dev": true, - "requires": { - "symbol-observable": "^1.1.0" - } - }, "is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -3366,12 +3282,6 @@ "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", "dev": true }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -3609,19 +3519,21 @@ "dev": true }, "lint-staged": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.3.tgz", - "integrity": "sha512-o2OkLxgVns5RwSC5QF7waeAjJA5nz5gnUfqL311LkZcFipKV7TztrSlhNUK5nQX9H0E5NELAdduMQ+M/JPT7RQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.4.0.tgz", + "integrity": "sha512-uaiX4U5yERUSiIEQc329vhCTDDwUcSvKdRLsNomkYLRzijk3v8V9GWm2Nz0RMVB87VcuzLvtgy6OsjoH++QHIg==", "dev": true, "requires": { - "chalk": "^3.0.0", - "commander": "^4.0.1", - "cosmiconfig": "^6.0.0", + "chalk": "^4.1.0", + "cli-truncate": "^2.1.0", + "commander": "^6.0.0", + "cosmiconfig": "^7.0.0", "debug": "^4.1.1", "dedent": "^0.7.0", - "execa": "^3.4.0", - "listr": "^0.14.3", - "log-symbols": "^3.0.0", + "enquirer": "^2.3.6", + "execa": "^4.0.3", + "listr2": "^2.6.0", + "log-symbols": "^4.0.0", "micromatch": "^4.0.2", "normalize-path": "^3.0.0", "please-upgrade-node": "^3.2.0", @@ -3630,276 +3542,81 @@ }, "dependencies": { "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - } - } - }, - "listr": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", - "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", - "dev": true, - "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", - "is-observable": "^1.1.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.5.0", - "listr-verbose-renderer": "^0.5.0", - "p-map": "^2.0.0", - "rxjs": "^6.3.3" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } - } - }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", - "dev": true - }, - "listr-update-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", - "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "dev": true, - "requires": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" } }, "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "chalk": "^4.0.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", "dev": true } } }, - "listr-verbose-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", - "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "listr2": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.6.2.tgz", + "integrity": "sha512-6x6pKEMs8DSIpA/tixiYY2m/GcbgMplMVmhQAaLFxEtNSKLeWTGjtmU57xvv6QCm2XcqzyNXL/cTSVf4IChCRA==", "dev": true, "requires": { - "chalk": "^2.4.1", - "cli-cursor": "^2.1.0", - "date-fns": "^1.27.2", - "figures": "^2.0.0" + "chalk": "^4.1.0", + "cli-truncate": "^2.1.0", + "figures": "^3.2.0", + "indent-string": "^4.0.0", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rxjs": "^6.6.2", + "through": "^2.3.8" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } } } @@ -3994,95 +3711,26 @@ } }, "log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { + "slice-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" } } } @@ -4792,14 +4440,6 @@ "dev": true, "requires": { "path-key": "^3.0.0" - }, - "dependencies": { - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - } } }, "nth-check": { @@ -4817,12 +4457,6 @@ "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4913,12 +4547,6 @@ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -5687,9 +5315,9 @@ "dev": true }, "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -6288,12 +5916,6 @@ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -6420,6 +6042,12 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, "time-zone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", @@ -6484,9 +6112,9 @@ "dev": true }, "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", "dev": true }, "type-check": { diff --git a/package.json b/package.json index 6215c016e5..7ff7219695 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "geckodriver": "^1.19.1", "glob": "^7.1.6", "husky": "^4.2.5", - "lint-staged": "^10.1.3", + "lint-staged": "^10.4.0", "npm-merge-driver": "^2.3.6", "run-node": "^2.0.0", "selenium-webdriver": "^4.0.0-alpha.7", From 4d85e90115ed84aa22a0b8b24db9918379b33516 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2020 15:12:11 -0700 Subject: [PATCH 16/34] Infrastructure: Bump husky package from 4.2.5 to 4.3.0 (pull #1543) Bumps [husky](https://github.com/typicode/husky) from 4.2.5 to 4.3.0. - [Release notes](https://github.com/typicode/husky/releases) - [Commits](https://github.com/typicode/husky/compare/v4.2.5...v4.3.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 58 ++++++++++++++++------------------------------- package.json | 2 +- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe1c9ba080..77b0fd4d9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -319,15 +319,6 @@ "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, - "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", @@ -1498,27 +1489,27 @@ "dev": true }, "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" }, "dependencies": { "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } } @@ -3018,15 +3009,15 @@ "dev": true }, "husky": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", - "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz", + "integrity": "sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA==", "dev": true, "requires": { "chalk": "^4.0.0", "ci-info": "^2.0.0", "compare-versions": "^3.6.0", - "cosmiconfig": "^6.0.0", + "cosmiconfig": "^7.0.0", "find-versions": "^3.2.0", "opencollective-postinstall": "^2.0.2", "pkg-dir": "^4.2.0", @@ -4482,9 +4473,9 @@ } }, "opencollective-postinstall": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", - "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "dev": true }, "optionator": { @@ -5133,12 +5124,6 @@ "strip-indent": "^3.0.0" } }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", @@ -6482,13 +6467,10 @@ "dev": true }, "yaml": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz", - "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.7" - } + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true }, "yargs": { "version": "15.4.1", diff --git a/package.json b/package.json index 7ff7219695..f2cc6f5c09 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "eslint": "^7.10.0", "geckodriver": "^1.19.1", "glob": "^7.1.6", - "husky": "^4.2.5", + "husky": "^4.3.0", "lint-staged": "^10.4.0", "npm-merge-driver": "^2.3.6", "run-node": "^2.0.0", From bdaa4c8e82b10ae106f579a3c46132f165177707 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 12 Oct 2020 03:20:35 -0400 Subject: [PATCH 17/34] Infrastructure: Stop linting Deleted Section Archive (pull #1550) --- .vnurc | 2 -- package.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.vnurc b/.vnurc index d648f40d43..9574520e99 100644 --- a/.vnurc +++ b/.vnurc @@ -10,5 +10,3 @@ Attribute “aria-posinset” not allowed on element “tr” at this point. Attribute “aria-setsize” not allowed on element “tr” at this point. # Ignoring role meter Bad value “meter” for attribute “role” on element “div”. -# Deleted Section Archive -The “longdesc” attribute on the “img” element is obsolete. Use a regular “a” element to link to the description. diff --git a/package.json b/package.json index f2cc6f5c09..7f218a4df6 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "regression": "ava --timeout=1m", "regression-report": "node test/util/report", "test": "npm run lint && npm run regression", - "vnu-jar": "java -jar node_modules/vnu-jar/build/dist/vnu.jar --errors-only --filterfile .vnurc --no-langdetect --skip-non-html *.html examples/" + "vnu-jar": "java -jar node_modules/vnu-jar/build/dist/vnu.jar --errors-only --filterfile .vnurc --no-langdetect --skip-non-html aria-practices.html examples/" }, "repository": { "type": "git", From 82a160da57eb00a9a0e834bd4c4654632fcbaf71 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 12 Oct 2020 03:44:05 -0400 Subject: [PATCH 18/34] Infrastructure: Adjust GitHub Action key names (pull #1549) Used for the protected branch UI, and this allows individual targeting of jobs to be "Required" for PRs. Co-authored-by: Matt King --- .github/workflows/lint-css.yml | 2 +- .github/workflows/lint-js.yml | 2 +- .github/workflows/regression.yml | 2 +- .github/workflows/spelling.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index 6184d9494c..3b29eb1dcf 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -19,7 +19,7 @@ on: - "!common/**" jobs: - lint: + lint-css: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 7360b337e8..4c616080e6 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -17,7 +17,7 @@ on: - "!common/**" jobs: - lint: + lint-js: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index ece0abea1a..10c736a672 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -20,7 +20,7 @@ on: - "!examples/landmarks/**" jobs: - deploy: + regression: runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 2c00ee6550..fc0b0feb11 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -20,7 +20,7 @@ on: - ".github/workflows/spelling.yml" jobs: - lint: + spelling: runs-on: ubuntu-latest steps: From 51203bd7a5d6ec5cee52ed086bb7aa9c5fa94923 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 12 Oct 2020 03:49:14 -0400 Subject: [PATCH 19/34] Infrastructure: Disable push builds for Dependabot (pull #1551) They get run as part of the PRs submited and avoids a double build Co-authored-by: Matt King --- .github/workflows/lint-js.yml | 2 ++ .github/workflows/spelling.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 4c616080e6..d71c6d8886 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -1,6 +1,8 @@ name: JavaScript Linting on: push: + branches-ignore: + - "dependabot/**" paths: - "package*.json" - ".eslint*" diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index fc0b0feb11..610bab9dab 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -2,6 +2,8 @@ name: Spellcheck on: push: + branches-ignore: + - "dependabot/**" paths: - "package*.json" - "cspell.json" From 74fd381e4f8694d55a68c6ce074463e04559bcb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 01:09:48 -0700 Subject: [PATCH 20/34] Infrastructure: Update actions/setup-node requirement to v2.1.2 (pull #1555) Updates the requirements on [actions/setup-node](https://github.com/actions/setup-node) to permit the latest version. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/commits/c6fd00ceb9747fb23ffdf72987450a2664414867) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-css.yml | 2 +- .github/workflows/lint-js.yml | 2 +- .github/workflows/regression.yml | 2 +- .github/workflows/spelling.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index 3b29eb1dcf..dd270e4660 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v2 - name: Set up Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2.1.2 - uses: xt0rted/stylelint-problem-matcher@v1 diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index d71c6d8886..47da208d2f 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -29,7 +29,7 @@ jobs: # setup-node task is used without a particular version in order to load # the ESLint problem matchers - name: Set up Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2.1.2 - name: Install npm dependencies run: npm ci diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 10c736a672..999e880757 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -33,7 +33,7 @@ jobs: fetch-depth: 0 - name: Set up Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2.1.2 - name: Install npm dependencies run: npm ci diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 610bab9dab..71b5b73328 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@v2 - name: Set up Node.js - uses: actions/setup-node@v2.1.1 + uses: actions/setup-node@v2.1.2 - name: Install npm dependencies run: npm ci From 7a8f135106b4b04a77deaf9089f752a2fafb29db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 01:28:03 -0700 Subject: [PATCH 21/34] Infrastructure: Bump geckodriver from 1.19.1 to 1.20.0 (pull #1538) Bumps [geckodriver](https://github.com/vladikoff/node-geckodriver) from 1.19.1 to 1.20.0. - [Release notes](https://github.com/vladikoff/node-geckodriver/releases) - [Commits](https://github.com/vladikoff/node-geckodriver/compare/v1.19.1...v1.20.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 135 ++++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 59 insertions(+), 78 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77b0fd4d9f..f62b8049e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -708,18 +708,18 @@ "dev": true }, "adm-zip": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", - "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true }, "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "debug": "4" } }, "aggregate-error": { @@ -1022,9 +1022,9 @@ "dev": true }, "bluebird": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.6.tgz", - "integrity": "sha1-AdqNgh2HgT0ViWfnQ9X+bGLPjA8=", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, "blueimp-md5": { @@ -1248,9 +1248,9 @@ } }, "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, "chunkd": { @@ -2274,21 +2274,6 @@ "is-arrayish": "^0.2.1" } }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, "escalade": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", @@ -2649,12 +2634,12 @@ } }, "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "requires": { - "minipass": "^2.6.0" + "minipass": "^3.0.0" } }, "fs.realpath": { @@ -2677,16 +2662,16 @@ "dev": true }, "geckodriver": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-1.19.1.tgz", - "integrity": "sha512-xWL/+eEhQ6+t98rc1c+xVM3hshDJibXtZf9WJA3sshxq4k5L1PBwfmswyBmmlKUfBr4xuC256gLVC2RxFhiCsQ==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-1.20.0.tgz", + "integrity": "sha512-5nVF4ixR+ZGhVsc4udnVihA9RmSlO6guPV1d2HqxYsgAOUNh0HfzxbzG7E49w4ilXq/CSu87x9yWvrsOstrADQ==", "dev": true, "requires": { - "adm-zip": "0.4.11", - "bluebird": "3.4.6", + "adm-zip": "0.4.16", + "bluebird": "3.7.2", "got": "5.6.0", - "https-proxy-agent": "3.0.0", - "tar": "4.4.2" + "https-proxy-agent": "5.0.0", + "tar": "6.0.2" }, "dependencies": { "got": { @@ -2982,24 +2967,13 @@ "dev": true }, "https-proxy-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz", - "integrity": "sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "agent-base": "6", + "debug": "4" } }, "human-signals": { @@ -3956,22 +3930,22 @@ } }, "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "yallist": "^4.0.0" } }, "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { - "minipass": "^2.9.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" } }, "mkdirp": { @@ -5995,18 +5969,25 @@ } }, "tar": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.2.tgz", - "integrity": "sha512-BfkE9CciGGgDsATqkikUHrQrraBCO+ke/1f6SFAEMnxyyfN9lxC+nW1NFWMpqH865DhHIy9vQi682gk1X7friw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", + "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", "dev": true, "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } } }, "temp-dir": { @@ -6461,9 +6442,9 @@ "dev": true }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "yaml": { diff --git a/package.json b/package.json index 7f218a4df6..174e088de0 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "cheerio": "^1.0.0-rc.2", "cspell": "^4.1.0", "eslint": "^7.10.0", - "geckodriver": "^1.19.1", + "geckodriver": "^1.20.0", "glob": "^7.1.6", "husky": "^4.3.0", "lint-staged": "^10.4.0", From 2cf2233624aba19dc302d70ad55556c5fc83b815 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 01:33:22 -0700 Subject: [PATCH 22/34] Infrastructure: Bump ava from 3.10.1 to 3.13.0 (pull #1537) Bumps [ava](https://github.com/avajs/ava) from 3.10.1 to 3.13.0. - [Release notes](https://github.com/avajs/ava/releases) - [Commits](https://github.com/avajs/ava/compare/v3.10.1...v3.13.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 376 +++++++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 152 insertions(+), 226 deletions(-) diff --git a/package-lock.json b/package-lock.json index f62b8049e9..29d8d9827e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -643,22 +643,6 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, "@types/minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", @@ -702,9 +686,9 @@ "dev": true }, "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.0.tgz", + "integrity": "sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==", "dev": true }, "adm-zip": { @@ -903,20 +887,20 @@ } }, "ava": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/ava/-/ava-3.10.1.tgz", - "integrity": "sha512-+w86ZHyFHIGCABi7NUrn/WJMyC+fDj0BSIlFNVS45WDKAD5vxbIiDWeclctxOOc2KDPfQD7tFOURSBz0FBLD0A==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/ava/-/ava-3.13.0.tgz", + "integrity": "sha512-yzky+gark5PdsFFlZ4CnBVxm/OgBUWtn9vAsSSnuooVJNOk5ER17HJXVeUzy63LIt06Zy34oThcn+2ZqgMs7SA==", "dev": true, "requires": { "@concordance/react": "^2.0.0", - "acorn": "^7.3.1", - "acorn-walk": "^7.2.0", + "acorn": "^8.0.1", + "acorn-walk": "^8.0.0", "ansi-styles": "^4.2.1", "arrgv": "^1.0.2", "arrify": "^2.0.1", "callsites": "^3.1.0", "chalk": "^4.1.0", - "chokidar": "^3.4.0", + "chokidar": "^3.4.2", "chunkd": "^2.0.1", "ci-info": "^2.0.0", "ci-parallel-vars": "^1.0.1", @@ -925,12 +909,12 @@ "cli-truncate": "^2.1.0", "code-excerpt": "^3.0.0", "common-path-prefix": "^3.0.0", - "concordance": "^5.0.0", + "concordance": "^5.0.1", "convert-source-map": "^1.7.0", "currently-unhandled": "^0.4.1", - "debug": "^4.1.1", - "del": "^5.1.0", - "emittery": "^0.7.0", + "debug": "^4.2.0", + "del": "^6.0.0", + "emittery": "^0.7.1", "equal-length": "^1.0.0", "figures": "^3.2.0", "globby": "^11.0.1", @@ -938,19 +922,20 @@ "import-local": "^3.0.2", "indent-string": "^4.0.0", "is-error": "^2.2.2", - "is-plain-object": "^3.0.1", + "is-plain-object": "^5.0.0", "is-promise": "^4.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.20", "matcher": "^3.0.0", "md5-hex": "^3.0.1", - "mem": "^6.1.0", + "mem": "^6.1.1", "ms": "^2.1.2", - "ora": "^4.0.4", + "ora": "^5.1.0", + "p-event": "^4.2.0", "p-map": "^4.0.0", "picomatch": "^2.2.2", "pkg-conf": "^3.1.0", "plur": "^4.0.0", - "pretty-ms": "^7.0.0", + "pretty-ms": "^7.0.1", "read-pkg": "^5.2.0", "resolve-cwd": "^3.0.0", "slash": "^3.0.0", @@ -960,15 +945,15 @@ "supertap": "^1.0.0", "temp-dir": "^2.0.0", "trim-off-newlines": "^1.0.1", - "update-notifier": "^4.1.0", + "update-notifier": "^4.1.1", "write-file-atomic": "^3.0.3", - "yargs": "^15.4.0" + "yargs": "^16.0.3" }, "dependencies": { "acorn": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", - "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz", + "integrity": "sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==", "dev": true }, "chalk": { @@ -981,24 +966,19 @@ "supports-color": "^7.1.0" } }, - "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "dev": true, "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" + "ms": "2.1.2" } }, - "is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true } } @@ -1028,9 +1008,9 @@ "dev": true }, "blueimp-md5": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.16.0.tgz", - "integrity": "sha512-j4nzWIqEFpLSbdhUApHRGDwfXbV8ALhqOn+FY5L6XBdKPAXU9BpGgFSbDsgqogfqPPR9R2WooseWCsfhfEC6uQ==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz", + "integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q==", "dev": true }, "boolbase": { @@ -1126,9 +1106,9 @@ }, "dependencies": { "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -1232,9 +1212,9 @@ } }, "chokidar": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", - "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", "dev": true, "requires": { "anymatch": "~3.1.1", @@ -1284,9 +1264,9 @@ "dev": true }, "cli-boxes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", - "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true }, "cli-cursor": { @@ -1315,14 +1295,27 @@ } }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.1.tgz", + "integrity": "sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } } }, "clone": { @@ -1430,9 +1423,9 @@ "dev": true }, "concordance": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.0.tgz", - "integrity": "sha512-stOCz9ffg0+rytwTaL2njUOIyMfANwfwmqc9Dr4vTUS/x/KkVFlWx9Zlzu6tHYtjKxxaCF/cEAZgPDac+n35sg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.1.tgz", + "integrity": "sha512-TbNtInKVElgEBnJ1v2Xg+MFX2lvFLbmlv3EuSC5wTfCwpB8kC3w3mffF6cKuUhkn475Ym1f1I4qmuXzx2+uXpw==", "dev": true, "requires": { "date-time": "^3.1.0", @@ -2072,45 +2065,26 @@ "dev": true }, "del": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", - "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", "dev": true, "requires": { - "globby": "^10.0.1", - "graceful-fs": "^4.2.2", + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", "is-glob": "^4.0.1", "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.1", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", "slash": "^3.0.0" }, "dependencies": { - "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true } } }, @@ -3242,9 +3216,15 @@ "dev": true }, "is-plain-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", - "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true }, "is-redirect": { @@ -3615,64 +3595,12 @@ "dev": true }, "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "chalk": "^4.0.0" } }, "log-update": { @@ -3801,9 +3729,9 @@ } }, "mem": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-6.1.0.tgz", - "integrity": "sha512-RlbnLQgRHk5lwqTtpEkBTQ2ll/CG/iB+J4Hy2Wh97PjgZgXgWJWrFF+XXujh3UUVLvR4OOTgZzcWMMwnehlEUg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-6.1.1.tgz", + "integrity": "sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q==", "dev": true, "requires": { "map-age-cleaner": "^0.1.3", @@ -3811,9 +3739,9 @@ }, "dependencies": { "mimic-fn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.0.0.tgz", - "integrity": "sha512-PiVO95TKvhiwgSwg1IdLYlCTdul38yZxZMIcnDSFIBUm4BNZha2qpQ4GpJ++15bHoKDtrW2D69lMfFwdFYtNZQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", "dev": true } } @@ -4467,25 +4395,25 @@ } }, "ora": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.5.tgz", - "integrity": "sha512-jCDgm9DqvRcNIAEv2wZPrh7E5PcQiDUnbnWbAfu4NGAE2ZNqPFbDixmWldy1YG2QfLeQhuiu6/h5VRrk6cG50w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.1.0.tgz", + "integrity": "sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==", "dev": true, "requires": { - "chalk": "^3.0.0", + "chalk": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-spinners": "^2.2.0", + "cli-spinners": "^2.4.0", "is-interactive": "^1.0.0", - "log-symbols": "^3.0.0", + "log-symbols": "^4.0.0", "mute-stream": "0.0.8", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" }, "dependencies": { "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -4512,6 +4440,21 @@ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "dev": true, + "requires": { + "p-timeout": "^3.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4539,6 +4482,15 @@ "aggregate-error": "^3.0.0" } }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -4918,9 +4870,9 @@ "dev": true }, "pretty-ms": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.0.tgz", - "integrity": "sha512-J3aPWiC5e9ZeZFuSeBraGxSkGMOvulSWsxDByOcbD1Pr75YL3LSNIKIb52WXbCLE1sS5s4inBBbryjF4Y05Ceg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", "dev": true, "requires": { "parse-ms": "^2.1.0" @@ -5197,12 +5149,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "resolve": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", @@ -5366,12 +5312,6 @@ "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=", "dev": true }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", @@ -6221,9 +6161,9 @@ "dev": true }, "update-notifier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", - "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", "dev": true, "requires": { "boxen": "^4.2.0", @@ -6358,12 +6298,6 @@ "isexe": "^2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "which-pm-runs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", @@ -6436,9 +6370,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.2.tgz", + "integrity": "sha512-CkwaeZw6dQgqgPGeTWKMXCRmMcBgETFlTml1+ZOO+q7kGst8NREJ+eWwFNPVUQ4QGdAaklbqCZHH6Zuep1RjiA==", "dev": true }, "yallist": { @@ -6454,33 +6388,25 @@ "dev": true }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.0.3.tgz", + "integrity": "sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.0", + "escalade": "^3.0.2", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "y18n": "^5.0.1", + "yargs-parser": "^20.0.0" } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-yYsjuSkjbLMBp16eaOt7/siKTjNVjMm3SoJnIg3sEh/JsvqVVDyjRKmaJV4cl+lNIgq6QEco2i3gDebJl7/vLA==", + "dev": true } } } diff --git a/package.json b/package.json index 174e088de0..43faafccd9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/w3c/aria-practices#readme", "devDependencies": { - "ava": "^3.7.1", + "ava": "^3.13.0", "cheerio": "^1.0.0-rc.2", "cspell": "^4.1.0", "eslint": "^7.10.0", From 78bb9a05a39a1fdfe5971d692e149cf65ae93780 Mon Sep 17 00:00:00 2001 From: James Nurthen Date: Mon, 12 Oct 2020 01:45:46 -0700 Subject: [PATCH 23/34] Infrastructure: Do not spell check idrefs in aria attribute values (pull #1560) Resolves issue #1559 by adding expressions to the ignore list in cspell.json that exclude the values of ARIA attributes, such as aria-labelledby, that have one or more IDREFs as their value. --- cspell.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cspell.json b/cspell.json index 9290c0804f..e4294e7258 100644 --- a/cspell.json +++ b/cspell.json @@ -238,8 +238,14 @@ "src=\"(?:[^\\\"]+|\\.)*\"", "class=\"(?:[^\\\"]+|\\.)*\"", "data-test-id=\"(?:[^\\\"]+|\\.)*\"", + "aria-activedescendant=\"(?:[^\\\"]+|\\.)*\"", "aria-controls=\"(?:[^\\\"]+|\\.)*\"", + "aria-describedby=\"(?:[^\\\"]+|\\.)*\"", + "aria-details=\"(?:[^\\\"]+|\\.)*\"", + "aria-errormessage=\"(?:[^\\\"]+|\\.)*\"", + "aria-flowto=\"(?:[^\\\"]+|\\.)*\"", "aria-labelledby=\"(?:[^\\\"]+|\\.)*\"", + "aria-owns=\"(?:[^\\\"]+|\\.)*\"", "href=\"(?:[^\\\"]+|\\.)*\"", "for=\"(?:[^\\\"]+|\\.)*\"", "data-(.*[=> ]?)" From 303191de951f67e4319393b2687f7effe0e4dcb7 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 21 Oct 2020 13:23:34 -0400 Subject: [PATCH 24/34] Infrastructure: Add Prettier Stylelint config (pull #1554) * Infrastructure: Add Prettier to Stylelint config * chore: Run Stylelint fix for Prettier config --- .github/workflows/lint-css.yml | 2 + .prettierignore | 2 + .prettierrc | 3 + .stylelintrc | 5 +- examples/breadcrumb/css/breadcrumb.css | 2 +- examples/button/css/button.css | 44 ++++- examples/carousel/css/carousel-prev-next.css | 1 - examples/carousel/css/carousel-tablist.css | 6 +- examples/checkbox/css/checkbox.css | 20 +- examples/combobox/css/select-only.css | 4 +- examples/css/core.css | 20 +- examples/dialog-modal/css/dialog.css | 9 +- examples/disclosure/css/disclosure-faq.css | 4 +- .../css/disclosure-img-long-description.css | 4 +- .../disclosure/css/disclosure-navigation.css | 178 +++++++++--------- examples/grid/css/dataGrids.css | 6 +- examples/grid/css/layoutGrids.css | 2 +- .../landmarks/css/bootstrap-accessibility.css | 44 +++-- examples/link/css/link.css | 2 +- examples/menubar/css/menubar-editor.css | 54 ++++-- examples/menubar/css/menubar-navigation.css | 2 +- examples/meter/css/meter.css | 3 +- examples/radio/css/radio.css | 14 +- examples/tabs/css/tabs.css | 6 +- examples/toolbar/css/menuButton.css | 27 +-- package-lock.json | 30 +++ package.json | 3 + 27 files changed, 323 insertions(+), 174 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index dd270e4660..e6f2acb9fe 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -5,6 +5,7 @@ on: - "dependabot/**" paths: - "package*.json" + - ".prettier*" - ".stylelint*" - "**/*.css" - ".github/workflows/lint-css.yml" @@ -13,6 +14,7 @@ on: pull_request: paths: - "package*.json" + - ".prettier*" - ".stylelint*" - "**/*.css" - ".github/workflows/lint-css.yml" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..07d52ad700 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore external copied w3c files +common diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..544138be45 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/.stylelintrc b/.stylelintrc index c0864c943b..25c15790a3 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,5 +1,5 @@ { - "extends": "stylelint-config-standard", + "extends": ["stylelint-config-standard", "stylelint-prettier/recommended"], "rules": { "media-feature-name-no-unknown": [ true, @@ -9,6 +9,7 @@ ] } ], - "no-descending-specificity": null + "no-descending-specificity": null, + "prettier/prettier": [true, {"singleQuote": false}] } } diff --git a/examples/breadcrumb/css/breadcrumb.css b/examples/breadcrumb/css/breadcrumb.css index 71e893d244..a5af4d1043 100644 --- a/examples/breadcrumb/css/breadcrumb.css +++ b/examples/breadcrumb/css/breadcrumb.css @@ -21,7 +21,7 @@ nav.breadcrumb li + li::before { transform: rotate(15deg); border-right: 0.1em solid currentColor; height: 0.8em; - content: ''; + content: ""; } nav.breadcrumb [aria-current="page"] { diff --git a/examples/button/css/button.css b/examples/button/css/button.css index 736b059da7..2658d6c1ec 100644 --- a/examples/button/css/button.css +++ b/examples/button/css/button.css @@ -8,13 +8,21 @@ color: #fff; text-shadow: 0 -1px 1px hsl(216, 27%, 25%); background-color: hsl(216, 82%, 51%); - background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%)); + background-image: linear-gradient( + to bottom, + hsl(216, 82%, 53%), + hsl(216, 82%, 47%) + ); } [role="button"]:hover { border-color: hsl(213, 71%, 29%); background-color: hsl(216, 82%, 31%); - background-image: linear-gradient(to bottom, hsl(216, 82%, 33%), hsl(216, 82%, 27%)); + background-image: linear-gradient( + to bottom, + hsl(216, 82%, 33%), + hsl(216, 82%, 27%) + ); cursor: default; } @@ -35,13 +43,17 @@ /* button border radius + outline width + offset */ border-radius: calc(5px + 3px + 3px); - content: ''; + content: ""; } [role="button"]:active { border-color: hsl(213, 71%, 49%); background-color: hsl(216, 82%, 31%); - background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%)); + background-image: linear-gradient( + to bottom, + hsl(216, 82%, 53%), + hsl(216, 82%, 47%) + ); box-shadow: inset 0 3px 5px 1px hsl(216, 82%, 30%); } @@ -50,13 +62,21 @@ box-shadow: 0 1px 2px hsl(261, 27%, 55%); text-shadow: 0 -1px 1px hsl(261, 27%, 25%); background-color: hsl(261, 82%, 51%); - background-image: linear-gradient(to bottom, hsl(261, 82%, 53%), hsl(261, 82%, 47%)); + background-image: linear-gradient( + to bottom, + hsl(261, 82%, 53%), + hsl(261, 82%, 47%) + ); } [role="button"][aria-pressed]:hover { border-color: hsl(261, 71%, 29%); background-color: hsl(261, 82%, 31%); - background-image: linear-gradient(to bottom, hsl(261, 82%, 33%), hsl(261, 82%, 27%)); + background-image: linear-gradient( + to bottom, + hsl(261, 82%, 33%), + hsl(261, 82%, 27%) + ); } [role="button"][aria-pressed="true"] { @@ -64,14 +84,22 @@ padding-bottom: 0.3em; border-color: hsl(261, 71%, 49%); background-color: hsl(261, 82%, 31%); - background-image: linear-gradient(to bottom, hsl(261, 82%, 63%), hsl(261, 82%, 57%)); + background-image: linear-gradient( + to bottom, + hsl(261, 82%, 63%), + hsl(261, 82%, 57%) + ); box-shadow: inset 0 3px 5px 1px hsl(261, 82%, 30%); } [role="button"][aria-pressed="true"]:hover { border-color: hsl(261, 71%, 49%); background-color: hsl(261, 82%, 31%); - background-image: linear-gradient(to bottom, hsl(261, 82%, 43%), hsl(261, 82%, 37%)); + background-image: linear-gradient( + to bottom, + hsl(261, 82%, 43%), + hsl(261, 82%, 37%) + ); box-shadow: inset 0 3px 5px 1px hsl(261, 82%, 20%); } diff --git a/examples/carousel/css/carousel-prev-next.css b/examples/carousel/css/carousel-prev-next.css index 72bce6b06c..c6c75d8d13 100644 --- a/examples/carousel/css/carousel-prev-next.css +++ b/examples/carousel/css/carousel-prev-next.css @@ -1,4 +1,3 @@ - /* .carousel */ img.reload { diff --git a/examples/carousel/css/carousel-tablist.css b/examples/carousel/css/carousel-tablist.css index bc139c3383..62b0c9a502 100644 --- a/examples/carousel/css/carousel-tablist.css +++ b/examples/carousel/css/carousel-tablist.css @@ -1,4 +1,3 @@ - /* .carousel-tablist */ img.reload { @@ -309,7 +308,10 @@ img.reload { } .carousel-tablist-moreaccessible .carousel-item .carousel-caption span.contrast, -.carousel-tablist-moreaccessible .carousel-item .carousel-caption span.contrast:hover { +.carousel-tablist-moreaccessible + .carousel-item + .carousel-caption + span.contrast:hover { background-color: transparent; } diff --git a/examples/checkbox/css/checkbox.css b/examples/checkbox/css/checkbox.css index fe30e07da5..3eb6b7dc84 100644 --- a/examples/checkbox/css/checkbox.css +++ b/examples/checkbox/css/checkbox.css @@ -17,7 +17,7 @@ ul.checkboxes { top: 50%; left: 7px; transform: translate(-50%, -50%); - content: ''; + content: ""; } [role="checkbox"]::before { @@ -29,14 +29,22 @@ ul.checkboxes { } [role="checkbox"]:active::before { - background-image: linear-gradient(to bottom, hsl(300, 3%, 73%), hsl(300, 3%, 93%) 30%); + background-image: linear-gradient( + to bottom, + hsl(300, 3%, 73%), + hsl(300, 3%, 93%) 30% + ); } [role="checkbox"][aria-checked="mixed"]::before, [role="checkbox"][aria-checked="true"]::before { border-color: hsl(216, 80%, 50%); background: hsl(217, 95%, 68%); - background-image: linear-gradient(to bottom, hsl(217, 95%, 68%), hsl(216, 80%, 57%)); + background-image: linear-gradient( + to bottom, + hsl(217, 95%, 68%), + hsl(216, 80%, 57%) + ); } [role="checkbox"][aria-checked="mixed"]::after { @@ -59,7 +67,11 @@ ul.checkboxes { [role="checkbox"][aria-checked="mixed"]:active::before, [role="checkbox"][aria-checked="true"]:active::before { - background-image: linear-gradient(to bottom, hsl(216, 80%, 57%), hsl(217, 95%, 68%)); + background-image: linear-gradient( + to bottom, + hsl(216, 80%, 57%), + hsl(217, 95%, 68%) + ); } [role="checkbox"]:focus { diff --git a/examples/combobox/css/select-only.css b/examples/combobox/css/select-only.css index cfb8fc0e65..1d8beb3a7e 100644 --- a/examples/combobox/css/select-only.css +++ b/examples/combobox/css/select-only.css @@ -14,7 +14,7 @@ .combo::after { border-bottom: 2px solid rgba(0, 0, 0, 0.75); border-right: 2px solid rgba(0, 0, 0, 0.75); - content: ''; + content: ""; display: block; height: 12px; pointer-events: none; @@ -93,7 +93,7 @@ .combo-option[aria-selected="true"]::after { border-bottom: 2px solid #000; border-right: 2px solid #000; - content: ''; + content: ""; height: 16px; position: absolute; right: 15px; diff --git a/examples/css/core.css b/examples/css/core.css index 3ce62a3541..628108dc37 100644 --- a/examples/css/core.css +++ b/examples/css/core.css @@ -73,13 +73,21 @@ table.data.attributes tbody td { font-size: inherit; text-shadow: 0 -1px 1px hsl(216, 27%, 25%); background-color: hsl(216, 82%, 51%); - background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%)); + background-image: linear-gradient( + to bottom, + hsl(216, 82%, 53%), + hsl(216, 82%, 47%) + ); } .example-header button:hover { border-color: hsl(213, 71%, 29%); background-color: hsl(216, 82%, 31%); - background-image: linear-gradient(to bottom, hsl(216, 82%, 33%), hsl(216, 82%, 27%)); + background-image: linear-gradient( + to bottom, + hsl(216, 82%, 33%), + hsl(216, 82%, 27%) + ); cursor: default; } @@ -100,12 +108,16 @@ table.data.attributes tbody td { /* button border radius + outline width + offset */ border-radius: calc(5px + 3px + 3px); - content: ''; + content: ""; } .example-header button:active { border-color: hsl(213, 71%, 49%); background-color: hsl(216, 82%, 31%); - background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%)); + background-image: linear-gradient( + to bottom, + hsl(216, 82%, 53%), + hsl(216, 82%, 47%) + ); box-shadow: inset 0 3px 5px 1px hsl(216, 82%, 30%); } diff --git a/examples/dialog-modal/css/dialog.css b/examples/dialog-modal/css/dialog.css index 9d095c9d20..58b6d54f1b 100644 --- a/examples/dialog-modal/css/dialog.css +++ b/examples/dialog-modal/css/dialog.css @@ -16,9 +16,12 @@ [role="dialog"] { position: absolute; top: 2rem; - left: 50vw; /* move to the middle of the screen (assumes relative parent is the body/viewport) */ - transform: translateX(-50%); /* move backwards 50% of this element's width */ - min-width: calc(640px - (15px * 2)); /* == breakpoint - left+right margin */ + left: 50vw; /* move to the middle of the screen (assumes relative parent is the body/viewport) */ + transform: translateX( + -50% + ); /* move backwards 50% of this element's width */ + + min-width: calc(640px - (15px * 2)); /* == breakpoint - left+right margin */ min-height: auto; box-shadow: 0 19px 38px rgba(0, 0, 0, 0.12), 0 15px 12px rgba(0, 0, 0, 0.22); } diff --git a/examples/disclosure/css/disclosure-faq.css b/examples/disclosure/css/disclosure-faq.css index c662b06b29..fb38732d5d 100644 --- a/examples/disclosure/css/disclosure-faq.css +++ b/examples/disclosure/css/disclosure-faq.css @@ -41,11 +41,11 @@ dl.faq button:active { } dl.faq button[aria-expanded="false"]::before { - content: url('../images/right-arrow-brown.png'); + content: url("../images/right-arrow-brown.png"); padding-right: 0.35em; } dl.faq button[aria-expanded="true"]::before { - content: url('../images/down-arrow-brown.png'); + content: url("../images/down-arrow-brown.png"); padding-right: 0.35em; } diff --git a/examples/disclosure/css/disclosure-img-long-description.css b/examples/disclosure/css/disclosure-img-long-description.css index 59a3494230..99f1047955 100644 --- a/examples/disclosure/css/disclosure-img-long-description.css +++ b/examples/disclosure/css/disclosure-img-long-description.css @@ -29,12 +29,12 @@ figure button:active { } figure button[aria-expanded="false"]::before { - content: url('../images/right-arrow-brown.png'); + content: url("../images/right-arrow-brown.png"); padding-right: 0.25em; } figure button[aria-expanded="true"]::before { - content: url('../images/down-arrow-brown.png'); + content: url("../images/down-arrow-brown.png"); padding-right: 0.25em; } diff --git a/examples/disclosure/css/disclosure-navigation.css b/examples/disclosure/css/disclosure-navigation.css index 2e05a5dd0e..d65982ef78 100644 --- a/examples/disclosure/css/disclosure-navigation.css +++ b/examples/disclosure/css/disclosure-navigation.css @@ -1,89 +1,89 @@ -.disclosure-nav { - background-color: #eee; - display: flex; - list-style-type: none; - padding: 0; -} - -.disclosure-nav ul { - background-color: #eee; - border: 1px solid #005a9c; - border-top-width: 5px; - border-radius: 0 0 4px 4px; - display: block; - list-style-type: none; - margin: 0; - min-width: 200px; - padding: 0; - position: absolute; -} - -.disclosure-nav li { - margin: 0; -} - -.disclosure-nav ul a { - border: 0; - color: #000; - display: block; - margin: 0; - padding: 0.5em 1em; - text-decoration: underline; -} - -.disclosure-nav ul a:hover, -.disclosure-nav ul a:focus { - background-color: #ddd; - margin-bottom: 0; - text-decoration: none; -} - -.disclosure-nav ul a:focus { - outline: 5px solid rgba(0, 90, 156, 0.75); - position: relative; -} - -.disclosure-nav button { - align-items: center; - border: 1px solid transparent; - border-right-color: #ccc; - display: flex; - padding: 1em; -} - -.disclosure-nav button::after { - content: ""; - border-bottom: 1px solid #000; - border-right: 1px solid #000; - height: 0.5em; - margin-left: 0.75em; - width: 0.5em; - transform: rotate(45deg); -} - -.disclosure-nav button:focus { - border-color: #005a9c; - outline: 5px solid rgba(0, 90, 156, 0.75); - position: relative; -} - -.disclosure-nav button:hover, -.disclosure-nav button[aria-expanded=true] { - background-color: #005a9c; - color: #fff; -} - -.disclosure-nav button:hover::after, -.disclosure-nav button[aria-expanded=true]::after { - border-color: #fff; -} - -/* Styles for example page content section */ -.disclosure-pagecontent { - border: 1px solid #ccc; - padding: 1em; -} - -.disclosure-pagecontent h3 { - margin-top: 0.5em; -} +.disclosure-nav { + background-color: #eee; + display: flex; + list-style-type: none; + padding: 0; +} + +.disclosure-nav ul { + background-color: #eee; + border: 1px solid #005a9c; + border-top-width: 5px; + border-radius: 0 0 4px 4px; + display: block; + list-style-type: none; + margin: 0; + min-width: 200px; + padding: 0; + position: absolute; +} + +.disclosure-nav li { + margin: 0; +} + +.disclosure-nav ul a { + border: 0; + color: #000; + display: block; + margin: 0; + padding: 0.5em 1em; + text-decoration: underline; +} + +.disclosure-nav ul a:hover, +.disclosure-nav ul a:focus { + background-color: #ddd; + margin-bottom: 0; + text-decoration: none; +} + +.disclosure-nav ul a:focus { + outline: 5px solid rgba(0, 90, 156, 0.75); + position: relative; +} + +.disclosure-nav button { + align-items: center; + border: 1px solid transparent; + border-right-color: #ccc; + display: flex; + padding: 1em; +} + +.disclosure-nav button::after { + content: ""; + border-bottom: 1px solid #000; + border-right: 1px solid #000; + height: 0.5em; + margin-left: 0.75em; + width: 0.5em; + transform: rotate(45deg); +} + +.disclosure-nav button:focus { + border-color: #005a9c; + outline: 5px solid rgba(0, 90, 156, 0.75); + position: relative; +} + +.disclosure-nav button:hover, +.disclosure-nav button[aria-expanded="true"] { + background-color: #005a9c; + color: #fff; +} + +.disclosure-nav button:hover::after, +.disclosure-nav button[aria-expanded="true"]::after { + border-color: #fff; +} + +/* Styles for example page content section */ +.disclosure-pagecontent { + border: 1px solid #ccc; + padding: 1em; +} + +.disclosure-pagecontent h3 { + margin-top: 0.5em; +} diff --git a/examples/grid/css/dataGrids.css b/examples/grid/css/dataGrids.css index b375739890..6955a5a4f0 100644 --- a/examples/grid/css/dataGrids.css +++ b/examples/grid/css/dataGrids.css @@ -46,11 +46,11 @@ } .edit-text-button::after { - background-image: url('../imgs/pencil-icon.png'); + background-image: url("../imgs/pencil-icon.png"); background-position: center; background-repeat: no-repeat; background-size: 44px; - content: ' '; + content: " "; height: 17px; opacity: 0.6; position: absolute; @@ -83,7 +83,7 @@ position: fixed; height: 65px; width: 85px; - background: url('../imgs/black_keys.png') no-repeat; + background: url("../imgs/black_keys.png") no-repeat; background-size: contain; } diff --git a/examples/grid/css/layoutGrids.css b/examples/grid/css/layoutGrids.css index 05cb120651..5c5b2da2f8 100644 --- a/examples/grid/css/layoutGrids.css +++ b/examples/grid/css/layoutGrids.css @@ -8,7 +8,7 @@ position: fixed; height: 65px; width: 85px; - background: url('../imgs/black_keys.png') no-repeat; + background: url("../imgs/black_keys.png") no-repeat; background-size: contain; } diff --git a/examples/landmarks/css/bootstrap-accessibility.css b/examples/landmarks/css/bootstrap-accessibility.css index e771f43833..895898814c 100644 --- a/examples/landmarks/css/bootstrap-accessibility.css +++ b/examples/landmarks/css/bootstrap-accessibility.css @@ -1,12 +1,22 @@ -.btn:focus { outline: dotted 2px #000; } -div.active:focus { outline: dotted 1px #000; } -a:focus { outline: dotted 1px #000; } +.btn:focus { + outline: dotted 2px #000; +} +div.active:focus { + outline: dotted 1px #000; +} +a:focus { + outline: dotted 1px #000; +} .close:hover, -.close:focus { outline: dotted 1px #000; } +.close:focus { + outline: dotted 1px #000; +} .nav > li > a:hover, -.nav > li > a:focus { outline: dotted 1px #000; } +.nav > li > a:focus { + outline: dotted 1px #000; +} .carousel-inner > .item { position: absolute; @@ -17,11 +27,15 @@ a:focus { outline: dotted 1px #000; } -o-transition: 0.6s ease-in-out left; transition: 0.6s ease-in-out left; } -.carousel-inner > .active { top: 0; } +.carousel-inner > .active { + top: 0; +} .carousel-inner > .active, .carousel-inner > .next, -.carousel-inner > .prev { position: relative; } +.carousel-inner > .prev { + position: relative; +} .carousel-inner > .next, .carousel-inner > .prev { @@ -29,12 +43,20 @@ a:focus { outline: dotted 1px #000; } top: 0; width: 100%; } -.alert-success { color: #2d4821; } -.alert-info { color: #214c62; } +.alert-success { + color: #2d4821; +} +.alert-info { + color: #214c62; +} .alert-warning { color: #6c4a00; background-color: #f9f1c6; } -.alert-danger { color: #d2322d; } -.alert-danger:hover { color: #a82824; } +.alert-danger { + color: #d2322d; +} +.alert-danger:hover { + color: #a82824; +} diff --git a/examples/link/css/link.css b/examples/link/css/link.css index caa101e904..920a8343d1 100644 --- a/examples/link/css/link.css +++ b/examples/link/css/link.css @@ -21,7 +21,7 @@ [role="link"].link3::before { display: block; - content: url('../images/w3c-logo.png'); + content: url("../images/w3c-logo.png"); width: 153px; height: 104px; } diff --git a/examples/menubar/css/menubar-editor.css b/examples/menubar/css/menubar-editor.css index edcaa3cca3..72a3f573c6 100644 --- a/examples/menubar/css/menubar-editor.css +++ b/examples/menubar/css/menubar-editor.css @@ -44,16 +44,19 @@ } .menubar-editor [role="menubar"] > li > [role="menuitem"]::after { - content: url('../images/down-arrow.svg'); + content: url("../images/down-arrow.svg"); padding-left: 0.25em; } .menubar-editor [role="menubar"] > li > [role="menuitem"]:focus::after { - content: url('../images/down-arrow-focus.svg'); + content: url("../images/down-arrow-focus.svg"); } -.menubar-editor [role="menubar"] > li > [role="menuitem"][aria-expanded="true"]::after { - content: url('../images/up-arrow-focus.svg'); +.menubar-editor + [role="menubar"] + > li + > [role="menuitem"][aria-expanded="true"]::after { + content: url("../images/up-arrow-focus.svg"); } .menubar-editor [role="menubar"] [role="menu"] { @@ -103,24 +106,28 @@ .menubar-editor [role="menubar"] [role="separator"] { padding-top: 3px; - background-image: url('../images/separator.svg'); + background-image: url("../images/separator.svg"); background-position: center; background-repeat: repeat-x; } -.menubar-editor [role="menubar"] [role="menu"] [aria-checked='true'] { +.menubar-editor [role="menubar"] [role="menu"] [aria-checked="true"] { padding: 6px; padding-left: 8px; padding-right: 18px; } -.menubar-editor [role="menubar"] [role='menuitemradio'][aria-checked='true']::before { - content: url('../images/radio-checked.svg'); +.menubar-editor + [role="menubar"] + [role="menuitemradio"][aria-checked="true"]::before { + content: url("../images/radio-checked.svg"); padding-right: 3px; } -.menubar-editor [role="menubar"] [role='menuitemcheckbox'][aria-checked='true']::before { - content: url('../images/checkbox-checked.svg'); +.menubar-editor + [role="menubar"] + [role="menuitemcheckbox"][aria-checked="true"]::before { + content: url("../images/checkbox-checked.svg"); padding-right: 3px; } @@ -136,13 +143,17 @@ outline: none; } -.menubar-editor [role="menubar"] [role='menuitemradio'][aria-checked='true']:focus::before { - content: url('../images/radio-checked-focus.svg'); +.menubar-editor + [role="menubar"] + [role="menuitemradio"][aria-checked="true"]:focus::before { + content: url("../images/radio-checked-focus.svg"); padding-right: 3px; } -.menubar-editor [role="menubar"] [role='menuitemcheckbox'][aria-checked='true']:focus::before { - content: url('../images/checkbox-checked-focus.svg'); +.menubar-editor + [role="menubar"] + [role="menuitemcheckbox"][aria-checked="true"]:focus::before { + content: url("../images/checkbox-checked-focus.svg"); padding-right: 3px; } @@ -157,9 +168,18 @@ padding-left: 25px; } -.menubar-editor [role="menubar"] [role="menu"] [role="menuitem"][aria-checked='true']:focus, -.menubar-editor [role="menubar"] [role="menu"] [role="menuitemcheckbox"][aria-checked='true']:focus, -.menubar-editor [role="menubar"] [role="menu"] [role="menuitemradio"][aria-checked='true']:focus { +.menubar-editor + [role="menubar"] + [role="menu"] + [role="menuitem"][aria-checked="true"]:focus, +.menubar-editor + [role="menubar"] + [role="menu"] + [role="menuitemcheckbox"][aria-checked="true"]:focus, +.menubar-editor + [role="menubar"] + [role="menu"] + [role="menuitemradio"][aria-checked="true"]:focus { padding-left: 8px; padding-right: 21px; } diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 54361e90c7..dddce81d00 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -87,7 +87,7 @@ .menubar-navigation [role="separator"] { padding-top: 3px; - background-image: url('../images/separator.svg'); + background-image: url("../images/separator.svg"); background-position: center; background-repeat: repeat-x; } diff --git a/examples/meter/css/meter.css b/examples/meter/css/meter.css index 75934ca1e8..8fc608b019 100644 --- a/examples/meter/css/meter.css +++ b/examples/meter/css/meter.css @@ -1,5 +1,4 @@ - -[role=meter] { +[role="meter"] { padding: 2px; width: 200px; height: 40px; diff --git a/examples/radio/css/radio.css b/examples/radio/css/radio.css index 6054273040..c8292223e3 100644 --- a/examples/radio/css/radio.css +++ b/examples/radio/css/radio.css @@ -30,7 +30,7 @@ top: 50%; left: 11px; transform: translate(-20%, -50%); - content: ''; + content: ""; } [role="radio"]::before { @@ -43,7 +43,11 @@ [role="radio"][aria-checked="true"]::before { border-color: hsl(216, 80%, 50%); background: hsl(217, 95%, 68%); - background-image: linear-gradient(to bottom, hsl(217, 95%, 68%), hsl(216, 80%, 57%)); + background-image: linear-gradient( + to bottom, + hsl(217, 95%, 68%), + hsl(216, 80%, 57%) + ); } [role="radio"][aria-checked="true"]::after { @@ -54,7 +58,11 @@ } [role="radio"][aria-checked="true"]:active::before { - background-image: linear-gradient(to bottom, hsl(216, 80%, 57%), hsl(217, 95%, 68%) 60%); + background-image: linear-gradient( + to bottom, + hsl(216, 80%, 57%), + hsl(217, 95%, 68%) 60% + ); } [role="radio"]:hover::before { diff --git a/examples/tabs/css/tabs.css b/examples/tabs/css/tabs.css index 286427fed1..405b4b9655 100644 --- a/examples/tabs/css/tabs.css +++ b/examples/tabs/css/tabs.css @@ -30,7 +30,7 @@ left: -1px; border-radius: 0.2em 0.2em 0 0; border-top: 3px solid hsl(20, 96%, 48%); - content: ''; + content: ""; } [role="tab"][aria-selected="true"] { @@ -52,7 +52,7 @@ height: 0.3em; background: hsl(220, 43%, 99%); box-shadow: none; - content: ''; + content: ""; } [role="tab"]:hover, @@ -95,7 +95,7 @@ left: -1px; border-bottom: 3px solid hsl(20, 96%, 48%); border-radius: 0 0 0.2em 0.2em; - content: ''; + content: ""; } [role="tabpanel"] p { diff --git a/examples/toolbar/css/menuButton.css b/examples/toolbar/css/menuButton.css index f49a807d14..39e272ca86 100644 --- a/examples/toolbar/css/menuButton.css +++ b/examples/toolbar/css/menuButton.css @@ -1,8 +1,8 @@ -[role='toolbar'] .menu-popup { +[role="toolbar"] .menu-popup { position: relative; } -[role='toolbar'] .menu-popup [role="menu"] { +[role="toolbar"] .menu-popup [role="menu"] { padding: 0; width: 9.5em; border: 2px solid #ddd; @@ -14,11 +14,11 @@ z-index: 1; } -[role='toolbar'] .menu-popup [role="menu"].focus { +[role="toolbar"] .menu-popup [role="menu"].focus { border-color: #005a9c; } -[role='toolbar'] .menu-popup [role="menu"] li { +[role="toolbar"] .menu-popup [role="menu"] li { padding: 0; margin: 0; display: block; @@ -26,7 +26,7 @@ list-style: none; } -[role='toolbar'] .menu-popup [role="menu"] [role="menuitemradio"] { +[role="toolbar"] .menu-popup [role="menu"] [role="menuitemradio"] { padding: 0; padding-top: 1px; padding-bottom: 1px; @@ -37,16 +37,19 @@ border-radius: 0; } -[role='toolbar'] .menu-popup [role="menu"] [role="menuitemradio"]::before { - content: url('../images/menuitemradio-unchecked.svg'); +[role="toolbar"] .menu-popup [role="menu"] [role="menuitemradio"]::before { + content: url("../images/menuitemradio-unchecked.svg"); } -[role='toolbar'] .menu-popup [role="menu"] [role="menuitemradio"][aria-checked="true"]::before { - content: url('../images/menuitemradio-checked.svg'); +[role="toolbar"] + .menu-popup + [role="menu"] + [role="menuitemradio"][aria-checked="true"]::before { + content: url("../images/menuitemradio-checked.svg"); } -[role='toolbar'] .menu-popup [role="menu"] [role="menuitemradio"]:focus, -[role='toolbar'] .menu-popup [role="menu"] [role="menuitemradio"]:hover { +[role="toolbar"] .menu-popup [role="menu"] [role="menuitemradio"]:focus, +[role="toolbar"] .menu-popup [role="menu"] [role="menuitemradio"]:hover { background: rgb(226, 239, 255); border-top: 1px solid #005a9c; border-bottom: 1px solid #005a9c; @@ -54,6 +57,6 @@ padding-bottom: 0; } -[role='toolbar'] .menu-popup [role="menu"] [role="menuitemradio"]:focus { +[role="toolbar"] .menu-popup [role="menu"] [role="menuitemradio"]:focus { border-color: #005a9c; } diff --git a/package-lock.json b/package-lock.json index 29d8d9827e..41c6ed9283 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4869,6 +4869,21 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "prettier": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", + "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", @@ -5734,6 +5749,12 @@ } } }, + "stylelint-config-prettier": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/stylelint-config-prettier/-/stylelint-config-prettier-8.0.2.tgz", + "integrity": "sha512-TN1l93iVTXpF9NJstlvP7nOu9zY2k+mN0NSFQ/VEGz15ZIP9ohdDZTtCWHs5LjctAhSAzaILULGbgiM0ItId3A==", + "dev": true + }, "stylelint-config-recommended": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz", @@ -5749,6 +5770,15 @@ "stylelint-config-recommended": "^3.0.0" } }, + "stylelint-prettier": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-1.1.2.tgz", + "integrity": "sha512-8QZ+EtBpMCXYB6cY0hNE3aCDKMySIx4Q8/malLaqgU/KXXa6Cj2KK8ulG1AJvUMD5XSSP8rOotqaCzR/BW6qAA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "sugarss": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", diff --git a/package.json b/package.json index 43faafccd9..132a35cb3c 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,13 @@ "husky": "^4.3.0", "lint-staged": "^10.4.0", "npm-merge-driver": "^2.3.6", + "prettier": "^2.1.2", "run-node": "^2.0.0", "selenium-webdriver": "^4.0.0-alpha.7", "stylelint": "^13.7.2", + "stylelint-config-prettier": "^8.0.2", "stylelint-config-standard": "^20.0.0", + "stylelint-prettier": "^1.1.2", "vnu-jar": "^20.3.16" }, "husky": { From d4e3284ec86e5c793365da4f04df28620f51fba0 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 21 Oct 2020 13:50:15 -0400 Subject: [PATCH 25/34] Infrastructure: Add Prettier ESLint config (pull #1553) * Infrastructure: Add Prettier for JS Formatting * Use the default settings and tie it to the current ESLint setup * chore: Run fix for Prettier JS formatting --- .eslintrc.json | 2 +- .github/workflows/lint-js.yml | 2 + examples/accordion/js/accordion.js | 245 +- examples/alert/js/alert.js | 30 +- examples/button/js/button.js | 34 +- examples/button/js/button_idl.js | 34 +- examples/carousel/js/carousel-prev-next.js | 259 ++- examples/carousel/js/carousel-tablist.js | 235 +- examples/checkbox/checkbox-1/js/checkbox.js | 46 +- .../checkbox/checkbox-2/js/checkboxMixed.js | 64 +- .../checkbox-2/js/controlledCheckbox.js | 38 +- examples/combobox/js/combobox-autocomplete.js | 240 +- examples/combobox/js/combobox-datepicker.js | 358 +-- examples/combobox/js/grid-combo-example.js | 15 +- examples/combobox/js/grid-combo.js | 67 +- examples/combobox/js/select-only.js | 149 +- examples/dialog-modal/js/alertdialog.js | 58 +- examples/dialog-modal/js/datepicker-dialog.js | 1596 ++++++------- examples/dialog-modal/js/dialog.js | 87 +- examples/disclosure/js/disclosureButton.js | 74 +- examples/disclosure/js/disclosureMenu.js | 94 +- examples/feed/js/feed.js | 14 +- examples/feed/js/feedDisplay.js | 127 +- examples/feed/js/main.js | 20 +- examples/grid/js/dataGrid.js | 185 +- examples/grid/js/dataGrids.js | 9 +- examples/grid/js/layoutGrids.js | 70 +- examples/grid/js/menuButton.js | 97 +- examples/js/app.js | 52 +- examples/js/examples.js | 151 +- examples/js/utils.js | 15 +- examples/landmarks/js/show.js | 10 +- examples/link/js/link.js | 13 +- examples/listbox/js/listbox-collapsible.js | 10 +- examples/listbox/js/listbox-rearrangeable.js | 50 +- examples/listbox/js/listbox-scrollable.js | 6 +- examples/listbox/js/listbox.js | 141 +- examples/listbox/js/toolbar.js | 11 +- examples/menu-button/js/MenuItemAction.js | 91 +- .../js/MenuItemActionActivedescendant.js | 48 +- examples/menu-button/js/MenuItemLinks.js | 95 +- examples/menu-button/js/Menubutton.js | 96 +- examples/menu-button/js/Menubutton2.js | 102 +- examples/menu-button/js/PopupMenuAction.js | 136 +- .../js/PopupMenuActionActivedescendant.js | 179 +- examples/menu-button/js/PopupMenuLinks.js | 123 +- examples/menubar/js/menubar-editor.js | 186 +- examples/menubar/js/menubar-navigation.js | 1249 +++++----- examples/menubar/js/style-manager.js | 32 +- examples/meter/js/meter.js | 17 +- examples/radio/js/radio-activedescendant.js | 48 +- examples/radio/js/radio.js | 38 +- examples/slider/js/multithumb-slider.js | 479 ++-- examples/slider/js/slider.js | 113 +- examples/slider/js/text-slider.js | 86 +- examples/slider/js/vertical-slider.js | 88 +- .../spinbutton/js/datepicker-spinbuttons.js | 116 +- examples/spinbutton/js/spinbutton-date.js | 98 +- examples/tabs/tabs-1/js/tabs.js | 59 +- examples/tabs/tabs-2/js/tabs.js | 55 +- examples/toolbar/js/FontMenu.js | 59 +- examples/toolbar/js/FontMenuButton.js | 26 +- examples/toolbar/js/FontMenuItem.js | 89 +- examples/toolbar/js/FormatToolbar.js | 124 +- examples/toolbar/js/FormatToolbarItem.js | 74 +- examples/toolbar/js/SpinButton.js | 61 +- examples/treegrid/js/treegrid-1.js | 150 +- examples/treeview/treeview-1/js/tree.js | 69 +- examples/treeview/treeview-1/js/treeitem.js | 73 +- .../treeview/treeview-1/js/treeitemClick.js | 18 +- examples/treeview/treeview-2/js/treeLinks.js | 75 +- .../treeview/treeview-2/js/treeitemLinks.js | 92 +- package-lock.json | 26 + package.json | 2 + respec-config.js | 144 +- scripts/coverage-report.js | 380 ++- scripts/reference-tables.js | 129 +- test/index.js | 14 +- test/tests/accordion_accordion.js | 849 ++++--- test/tests/alert_alert.js | 38 +- test/tests/breadcrumb_index.js | 59 +- test/tests/button_button.js | 274 ++- test/tests/carousel_carousel-1-prev-next.js | 536 +++-- test/tests/carousel_carousel-2-tablist.js | 886 ++++--- test/tests/checkbox_checkbox-1.js | 257 ++- test/tests/checkbox_checkbox-2.js | 389 ++-- test/tests/combobox_autocomplete-both.js | 802 ++++--- test/tests/combobox_autocomplete-list.js | 770 +++--- test/tests/combobox_autocomplete-none.js | 748 +++--- test/tests/combobox_datepicker.js | 816 ++++--- test/tests/combobox_grid-combo.js | 768 +++--- test/tests/combobox_select-only.js | 1546 +++++++++---- test/tests/dialog-modal_datepicker.js | 1493 +++++++----- test/tests/dialog-modal_dialog.js | 574 +++-- test/tests/disclosure_faq.js | 234 +- test/tests/disclosure_img-long-description.js | 271 ++- test/tests/disclosure_navigation.js | 680 ++++-- test/tests/feed_feed.js | 306 ++- test/tests/grid_LayoutGrids.js | 1256 ++++++---- test/tests/grid_dataGrids.js | 1232 ++++++---- test/tests/link_link.js | 105 +- test/tests/listbox_collapsible.js | 591 +++-- test/tests/listbox_grouped.js | 516 +++-- test/tests/listbox_rearrangeable.js | 1229 ++++++---- test/tests/listbox_scrollable.js | 191 +- .../menu-button_actions-active-descendant.js | 369 ++- test/tests/menu-button_actions.js | 541 +++-- test/tests/menu-button_links.js | 509 ++-- test/tests/menubar_menubar-editor.js | 1571 ++++++++----- test/tests/menubar_menubar-navigation.js | 2056 ++++++++++------- test/tests/meter_meter.js | 145 +- test/tests/radio_radio-activedescendant.js | 418 ++-- test/tests/radio_radio.js | 454 ++-- test/tests/slider_multithumb.js | 1833 ++++++++------- test/tests/slider_slider-1.js | 1330 ++++++----- test/tests/slider_slider-2.js | 1210 ++++++---- test/tests/spinbutton_datepicker.js | 347 ++- test/tests/table_table.js | 92 +- test/tests/tabs_tabs-1.js | 560 +++-- test/tests/tabs_tabs-2.js | 495 ++-- test/tests/toolbar_toolbar.js | 107 +- test/tests/treegrid_treegrid-1.js | 955 ++++---- test/tests/treeview_treeview-1a.js | 865 ++++--- test/tests/treeview_treeview-1b.js | 953 ++++---- test/tests/treeview_treeview-2a.js | 932 ++++---- test/tests/treeview_treeview-2b.js | 1149 +++++---- test/util/assertAriaActivedescendant.js | 16 +- test/util/assertAriaControls.js | 23 +- test/util/assertAriaDescribedby.js | 48 +- test/util/assertAriaLabelExists.js | 20 +- test/util/assertAriaLabelledby.js | 27 +- test/util/assertAriaRoles.js | 20 +- .../assertAriaSelectedAndActivedescendant.js | 21 +- test/util/assertAttributeDNE.js | 26 +- test/util/assertAttributeValues.js | 15 +- test/util/assertHasFocus.js | 52 +- test/util/assertNoElements.js | 6 +- test/util/assertRovingTabindex.js | 17 +- test/util/assertTabOrder.js | 4 +- test/util/force-serial.js | 29 +- test/util/get-json.js | 52 +- test/util/queryElement.js | 2 +- test/util/queryElements.js | 2 +- test/util/report.js | 86 +- test/util/start-geckodriver.js | 23 +- 145 files changed, 26340 insertions(+), 18303 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1190096235..004e91e3f0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": "eslint:recommended", + "extends": ["eslint:recommended", "plugin:prettier/recommended"], "env": { "browser": true }, diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 47da208d2f..2a2b7bee37 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -5,6 +5,7 @@ on: - "dependabot/**" paths: - "package*.json" + - ".prettier*" - ".eslint*" - "**/*.js" - ".github/workflows/lint-js.yml" @@ -13,6 +14,7 @@ on: pull_request: paths: - "package*.json" + - ".prettier*" - ".eslint*" - "**/*.js" - ".github/workflows/lint-js.yml" diff --git a/examples/accordion/js/accordion.js b/examples/accordion/js/accordion.js index 8d0b354ed0..e8b8531700 100644 --- a/examples/accordion/js/accordion.js +++ b/examples/accordion/js/accordion.js @@ -1,134 +1,143 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* Simple accordion pattern example -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * Simple accordion pattern example + */ 'use strict'; -Array.prototype.slice.call(document.querySelectorAll('.Accordion')).forEach(function (accordion) { - - // Allow for multiple accordion sections to be expanded at the same time - var allowMultiple = accordion.hasAttribute('data-allow-multiple'); - // Allow for each toggle to both open and close individually - var allowToggle = (allowMultiple) ? allowMultiple : accordion.hasAttribute('data-allow-toggle'); - - // Create the array of toggle elements for the accordion group - var triggers = Array.prototype.slice.call(accordion.querySelectorAll('.Accordion-trigger')); - var panels = Array.prototype.slice.call(accordion.querySelectorAll('.Accordion-panel')); - - - accordion.addEventListener('click', function (event) { - var target = event.target; - - if (target.classList.contains('Accordion-trigger')) { - // Check if the current toggle is expanded. - var isExpanded = target.getAttribute('aria-expanded') == 'true'; - var active = accordion.querySelector('[aria-expanded="true"]'); - - // without allowMultiple, close the open accordion - if (!allowMultiple && active && active !== target) { - // Set the expanded state on the triggering element - active.setAttribute('aria-expanded', 'false'); - // Hide the accordion sections, using aria-controls to specify the desired section - document.getElementById(active.getAttribute('aria-controls')).setAttribute('hidden', ''); - - // When toggling is not allowed, clean up disabled state - if (!allowToggle) { - active.removeAttribute('aria-disabled'); +Array.prototype.slice + .call(document.querySelectorAll('.Accordion')) + .forEach(function (accordion) { + // Allow for multiple accordion sections to be expanded at the same time + var allowMultiple = accordion.hasAttribute('data-allow-multiple'); + // Allow for each toggle to both open and close individually + var allowToggle = allowMultiple + ? allowMultiple + : accordion.hasAttribute('data-allow-toggle'); + + // Create the array of toggle elements for the accordion group + var triggers = Array.prototype.slice.call( + accordion.querySelectorAll('.Accordion-trigger') + ); + var panels = Array.prototype.slice.call( + accordion.querySelectorAll('.Accordion-panel') + ); + + accordion.addEventListener('click', function (event) { + var target = event.target; + + if (target.classList.contains('Accordion-trigger')) { + // Check if the current toggle is expanded. + var isExpanded = target.getAttribute('aria-expanded') == 'true'; + var active = accordion.querySelector('[aria-expanded="true"]'); + + // without allowMultiple, close the open accordion + if (!allowMultiple && active && active !== target) { + // Set the expanded state on the triggering element + active.setAttribute('aria-expanded', 'false'); + // Hide the accordion sections, using aria-controls to specify the desired section + document + .getElementById(active.getAttribute('aria-controls')) + .setAttribute('hidden', ''); + + // When toggling is not allowed, clean up disabled state + if (!allowToggle) { + active.removeAttribute('aria-disabled'); + } } - } - - if (!isExpanded) { - // Set the expanded state on the triggering element - target.setAttribute('aria-expanded', 'true'); - // Hide the accordion sections, using aria-controls to specify the desired section - document.getElementById(target.getAttribute('aria-controls')).removeAttribute('hidden'); - // If toggling is not allowed, set disabled state on trigger - if (!allowToggle) { - target.setAttribute('aria-disabled', 'true'); + if (!isExpanded) { + // Set the expanded state on the triggering element + target.setAttribute('aria-expanded', 'true'); + // Hide the accordion sections, using aria-controls to specify the desired section + document + .getElementById(target.getAttribute('aria-controls')) + .removeAttribute('hidden'); + + // If toggling is not allowed, set disabled state on trigger + if (!allowToggle) { + target.setAttribute('aria-disabled', 'true'); + } + } else if (allowToggle && isExpanded) { + // Set the expanded state on the triggering element + target.setAttribute('aria-expanded', 'false'); + // Hide the accordion sections, using aria-controls to specify the desired section + document + .getElementById(target.getAttribute('aria-controls')) + .setAttribute('hidden', ''); } - } - else if (allowToggle && isExpanded) { - // Set the expanded state on the triggering element - target.setAttribute('aria-expanded', 'false'); - // Hide the accordion sections, using aria-controls to specify the desired section - document.getElementById(target.getAttribute('aria-controls')).setAttribute('hidden', ''); - } - - event.preventDefault(); - } - }); - - // Bind keyboard behaviors on the main accordion container - accordion.addEventListener('keydown', function (event) { - var target = event.target; - var key = event.which.toString(); - - var isExpanded = target.getAttribute('aria-expanded') == 'true'; - var allowToggle = (allowMultiple) ? allowMultiple : accordion.hasAttribute('data-allow-toggle'); - - // 33 = Page Up, 34 = Page Down - var ctrlModifier = (event.ctrlKey && key.match(/33|34/)); - - // Is this coming from an accordion header? - if (target.classList.contains('Accordion-trigger')) { - // Up/ Down arrow and Control + Page Up/ Page Down keyboard operations - // 38 = Up, 40 = Down - if (key.match(/38|40/) || ctrlModifier) { - var index = triggers.indexOf(target); - var direction = (key.match(/34|40/)) ? 1 : -1; - var length = triggers.length; - var newIndex = (index + length + direction) % length; - - triggers[newIndex].focus(); event.preventDefault(); } - else if (key.match(/35|36/)) { - // 35 = End, 36 = Home keyboard operations - switch (key) { - // Go to first accordion - case '36': - triggers[0].focus(); - break; + }); + + // Bind keyboard behaviors on the main accordion container + accordion.addEventListener('keydown', function (event) { + var target = event.target; + var key = event.which.toString(); + + var isExpanded = target.getAttribute('aria-expanded') == 'true'; + var allowToggle = allowMultiple + ? allowMultiple + : accordion.hasAttribute('data-allow-toggle'); + + // 33 = Page Up, 34 = Page Down + var ctrlModifier = event.ctrlKey && key.match(/33|34/); + + // Is this coming from an accordion header? + if (target.classList.contains('Accordion-trigger')) { + // Up/ Down arrow and Control + Page Up/ Page Down keyboard operations + // 38 = Up, 40 = Down + if (key.match(/38|40/) || ctrlModifier) { + var index = triggers.indexOf(target); + var direction = key.match(/34|40/) ? 1 : -1; + var length = triggers.length; + var newIndex = (index + length + direction) % length; + + triggers[newIndex].focus(); + + event.preventDefault(); + } else if (key.match(/35|36/)) { + // 35 = End, 36 = Home keyboard operations + switch (key) { + // Go to first accordion + case '36': + triggers[0].focus(); + break; // Go to last accordion - case '35': - triggers[triggers.length - 1].focus(); - break; + case '35': + triggers[triggers.length - 1].focus(); + break; + } + event.preventDefault(); } - event.preventDefault(); - } - - } - }); - - // These are used to style the accordion when one of the buttons has focus - accordion.querySelectorAll('.Accordion-trigger').forEach(function (trigger) { - - trigger.addEventListener('focus', function (event) { - accordion.classList.add('focus'); - }); - - trigger.addEventListener('blur', function (event) { - accordion.classList.remove('focus'); }); - }); - - // Minor setup: will set disabled state, via aria-disabled, to an - // expanded/ active accordion which is not allowed to be toggled close - if (!allowToggle) { - // Get the first expanded/ active accordion - var expanded = accordion.querySelector('[aria-expanded="true"]'); - - // If an expanded/ active accordion is found, disable - if (expanded) { - expanded.setAttribute('aria-disabled', 'true'); + // These are used to style the accordion when one of the buttons has focus + accordion + .querySelectorAll('.Accordion-trigger') + .forEach(function (trigger) { + trigger.addEventListener('focus', function (event) { + accordion.classList.add('focus'); + }); + + trigger.addEventListener('blur', function (event) { + accordion.classList.remove('focus'); + }); + }); + + // Minor setup: will set disabled state, via aria-disabled, to an + // expanded/ active accordion which is not allowed to be toggled close + if (!allowToggle) { + // Get the first expanded/ active accordion + var expanded = accordion.querySelector('[aria-expanded="true"]'); + + // If an expanded/ active accordion is found, disable + if (expanded) { + expanded.setAttribute('aria-disabled', 'true'); + } } - } - -}); + }); diff --git a/examples/alert/js/alert.js b/examples/alert/js/alert.js index efc1d66be7..6bb39f055b 100644 --- a/examples/alert/js/alert.js +++ b/examples/alert/js/alert.js @@ -1,33 +1,29 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + */ 'use strict'; window.addEventListener('load', function () { - var button = document.getElementById('alert-trigger'); button.addEventListener('click', addAlert); - }); /* -* @function addAlert -* -* @desc Adds an alert to the page -* -* @param {Object} event - Standard W3C event object -* -*/ - -function addAlert (event) { - + * @function addAlert + * + * @desc Adds an alert to the page + * + * @param {Object} event - Standard W3C event object + * + */ + +function addAlert(event) { var example = document.getElementById('example'); var template = document.getElementById('alert-template').innerHTML; example.innerHTML = template; - } diff --git a/examples/button/js/button.js b/examples/button/js/button.js index 3aabb28521..5af4207faa 100644 --- a/examples/button/js/button.js +++ b/examples/button/js/button.js @@ -1,16 +1,16 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* JS code for the button design pattern -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * JS code for the button design pattern + */ 'use strict'; var ICON_MUTE_URL = 'images/mute.svg#icon-mute'; var ICON_SOUND_URL = 'images/mute.svg#icon-sound'; -function init () { +function init() { var actionButton = document.getElementById('action'); actionButton.addEventListener('click', activateActionButton); actionButton.addEventListener('keydown', actionButtonKeydownHandler); @@ -27,7 +27,7 @@ function init () { * * @param {KeyboardEvent} event */ -function actionButtonKeydownHandler (event) { +function actionButtonKeydownHandler(event) { // The action button is activated by space on the keyup event, but the // default action for space is already triggered on keydown. It needs to be // prevented to stop scrolling the page before activating the button. @@ -46,14 +46,14 @@ function actionButtonKeydownHandler (event) { * * @param {KeyboardEvent} event */ -function actionButtonKeyupHandler (event) { +function actionButtonKeyupHandler(event) { if (event.keyCode === 32) { event.preventDefault(); activateActionButton(); } } -function activateActionButton () { +function activateActionButton() { window.print(); } @@ -63,7 +63,7 @@ function activateActionButton () { * * @param {MouseEvent} event */ -function toggleButtonClickHandler (event) { +function toggleButtonClickHandler(event) { if ( event.currentTarget.tagName === 'button' || event.currentTarget.getAttribute('role') === 'button' @@ -77,11 +77,10 @@ function toggleButtonClickHandler (event) { * * @param {KeyboardEvent} event */ -function toggleButtonKeydownHandler (event) { +function toggleButtonKeydownHandler(event) { if (event.keyCode === 32) { event.preventDefault(); - } - else if (event.keyCode === 13) { + } else if (event.keyCode === 13) { event.preventDefault(); toggleButtonState(event.currentTarget); } @@ -92,7 +91,7 @@ function toggleButtonKeydownHandler (event) { * * @param {KeyboardEvent} event */ -function toggleButtonKeyupHandler (event) { +function toggleButtonKeyupHandler(event) { if (event.keyCode === 32) { event.preventDefault(); toggleButtonState(event.currentTarget); @@ -104,13 +103,16 @@ function toggleButtonKeyupHandler (event) { * * @param {HTMLElement} button */ -function toggleButtonState (button) { +function toggleButtonState(button) { var isAriaPressed = button.getAttribute('aria-pressed') === 'true'; button.setAttribute('aria-pressed', isAriaPressed ? 'false' : 'true'); var icon = button.querySelector('use'); - icon.setAttribute('xlink:href', isAriaPressed ? ICON_SOUND_URL : ICON_MUTE_URL); + icon.setAttribute( + 'xlink:href', + isAriaPressed ? ICON_SOUND_URL : ICON_MUTE_URL + ); } window.onload = init; diff --git a/examples/button/js/button_idl.js b/examples/button/js/button_idl.js index 85a37ef8f6..c2fc4fd0df 100644 --- a/examples/button/js/button_idl.js +++ b/examples/button/js/button_idl.js @@ -1,16 +1,16 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* JS code for the button design pattern using the new ARIA 1.2 IDL for reflection. -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * JS code for the button design pattern using the new ARIA 1.2 IDL for reflection. + */ 'use strict'; var ICON_MUTE_URL = 'images/mute.svg#icon-mute'; var ICON_SOUND_URL = 'images/mute.svg#icon-sound'; -function init () { +function init() { var actionButton = document.getElementById('action'); // Set role in js instead of html actionButton.role = 'button'; @@ -32,7 +32,7 @@ function init () { * * @param {KeyboardEvent} event */ -function actionButtonKeydownHandler (event) { +function actionButtonKeydownHandler(event) { // The action button is activated by space on the keyup event, but the // default action for space is already triggered on keydown. It needs to be // prevented to stop scrolling the page before activating the button. @@ -51,14 +51,14 @@ function actionButtonKeydownHandler (event) { * * @param {KeyboardEvent} event */ -function actionButtonKeyupHandler (event) { +function actionButtonKeyupHandler(event) { if (event.keyCode === 32) { event.preventDefault(); activateActionButton(); } } -function activateActionButton () { +function activateActionButton() { window.print(); } @@ -68,7 +68,7 @@ function activateActionButton () { * * @param {MouseEvent} event */ -function toggleButtonClickHandler (event) { +function toggleButtonClickHandler(event) { if ( event.currentTarget.tagName === 'button' || event.currentTarget.role === 'button' // This code is equivalent to: event.currentTarget.getAttribute('role') === 'button' @@ -82,11 +82,10 @@ function toggleButtonClickHandler (event) { * * @param {KeyboardEvent} event */ -function toggleButtonKeydownHandler (event) { +function toggleButtonKeydownHandler(event) { if (event.keyCode === 32) { event.preventDefault(); - } - else if (event.keyCode === 13) { + } else if (event.keyCode === 13) { event.preventDefault(); toggleButtonState(event.currentTarget); } @@ -97,7 +96,7 @@ function toggleButtonKeydownHandler (event) { * * @param {KeyboardEvent} event */ -function toggleButtonKeyupHandler (event) { +function toggleButtonKeyupHandler(event) { if (event.keyCode === 32) { event.preventDefault(); toggleButtonState(event.currentTarget); @@ -109,7 +108,7 @@ function toggleButtonKeyupHandler (event) { * * @param {HTMLElement} button */ -function toggleButtonState (button) { +function toggleButtonState(button) { // The following line of code is equivalent to: var isAriaPressed = button.getAttribute('aria-pressed') === 'true'; var isAriaPressed = button.ariaPressed === 'true'; @@ -117,7 +116,10 @@ function toggleButtonState (button) { button.ariaPressed = isAriaPressed ? 'false' : 'true'; var icon = button.querySelector('use'); - icon.setAttribute('xlink:href', isAriaPressed ? ICON_SOUND_URL : ICON_MUTE_URL); + icon.setAttribute( + 'xlink:href', + isAriaPressed ? ICON_SOUND_URL : ICON_MUTE_URL + ); } window.onload = init; diff --git a/examples/carousel/js/carousel-prev-next.js b/examples/carousel/js/carousel-prev-next.js index 67090b2e98..8315aa9720 100644 --- a/examples/carousel/js/carousel-prev-next.js +++ b/examples/carousel/js/carousel-prev-next.js @@ -1,18 +1,21 @@ /* -* File: carousel-prev-next.js -* -* Desc: Carousel widget with Previous and Next Buttons that implements ARIA Authoring Practices -* -*/ + * File: carousel-prev-next.js + * + * Desc: Carousel widget with Previous and Next Buttons that implements ARIA Authoring Practices + * + */ 'use strict'; var CarouselPreviousNext = function (node, options) { // merge passed options with defaults - options = Object.assign({ moreaccessible: false, paused: false, norotate: false }, (options || {})); + options = Object.assign( + { moreaccessible: false, paused: false, norotate: false }, + options || {} + ); // a prefers-reduced-motion user setting must always override autoplay - var hasReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); + var hasReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); if (hasReducedMotion.matches) { options.paused = true; } @@ -33,7 +36,7 @@ var CarouselPreviousNext = function (node, options) { /* State properties */ this.hasUserActivatedPlay = false; // set when the user activates the play/pause button - this.isAutoRotationDisabled = options.norotate // This property for disabling auto rotation + this.isAutoRotationDisabled = options.norotate; // This property for disabling auto rotation this.isPlayingEnabled = !options.paused; // This property is also set in updatePlaying method this.timeInterval = 5000; // length of slide rotation in ms this.currentIndex = 0; // index of current slide @@ -44,7 +47,10 @@ var CarouselPreviousNext = function (node, options) { var elem = document.querySelector('.carousel .controls button.rotation'); if (elem) { this.pausePlayButtonNode = elem; - this.pausePlayButtonNode.addEventListener('click', this.handlePausePlayButtonClick.bind(this)); + this.pausePlayButtonNode.addEventListener( + 'click', + this.handlePausePlayButtonClick.bind(this) + ); } // Previous Button @@ -52,9 +58,18 @@ var CarouselPreviousNext = function (node, options) { elem = document.querySelector('.carousel .controls button.previous'); if (elem) { this.previousButtonNode = elem; - this.previousButtonNode.addEventListener('click', this.handlePreviousButtonClick.bind(this)); - this.previousButtonNode.addEventListener('focus', this.handleFocusIn.bind(this)); - this.previousButtonNode.addEventListener('blur', this.handleFocusOut.bind(this)); + this.previousButtonNode.addEventListener( + 'click', + this.handlePreviousButtonClick.bind(this) + ); + this.previousButtonNode.addEventListener( + 'focus', + this.handleFocusIn.bind(this) + ); + this.previousButtonNode.addEventListener( + 'blur', + this.handleFocusOut.bind(this) + ); } // Next Button @@ -62,27 +77,44 @@ var CarouselPreviousNext = function (node, options) { elem = document.querySelector('.carousel .controls button.next'); if (elem) { this.nextButtonNode = elem; - this.nextButtonNode.addEventListener('click', this.handleNextButtonClick.bind(this)); - this.nextButtonNode.addEventListener('focus', this.handleFocusIn.bind(this)); - this.nextButtonNode.addEventListener('blur', this.handleFocusOut.bind(this)); + this.nextButtonNode.addEventListener( + 'click', + this.handleNextButtonClick.bind(this) + ); + this.nextButtonNode.addEventListener( + 'focus', + this.handleFocusIn.bind(this) + ); + this.nextButtonNode.addEventListener( + 'blur', + this.handleFocusOut.bind(this) + ); } // Carousel item events - for (var i = 0; i < this.carouselItemNodes.length; i++ ) { + for (var i = 0; i < this.carouselItemNodes.length; i++) { var caouselItemNode = this.carouselItemNodes[i]; // support stopping rotation when any element receives focus in the tabpanel caouselItemNode.addEventListener('focusin', this.handleFocusIn.bind(this)); - caouselItemNode.addEventListener('focusout', this.handleFocusOut.bind(this)); + caouselItemNode.addEventListener( + 'focusout', + this.handleFocusOut.bind(this) + ); var imageLinkNode = caouselItemNode.querySelector('.carousel-image a'); if (imageLinkNode) { - imageLinkNode.addEventListener('focus', this.handleImageLinkFocus.bind(this)); - imageLinkNode.addEventListener('blur', this.handleImageLinkBlur.bind(this)); + imageLinkNode.addEventListener( + 'focus', + this.handleImageLinkFocus.bind(this) + ); + imageLinkNode.addEventListener( + 'blur', + this.handleImageLinkBlur.bind(this) + ); } - } // Handle hover events @@ -95,38 +127,37 @@ var CarouselPreviousNext = function (node, options) { this.updatePlaying(!options.paused && !options.norotate); this.setAccessibleStyling(options.moreaccessible); this.rotateSlides(); -} +}; /* Public function to disable/enable rotation and if false, hide pause/play button*/ -CarouselPreviousNext.prototype.enableOrDisableAutoRotation = function(disable) { +CarouselPreviousNext.prototype.enableOrDisableAutoRotation = function ( + disable +) { this.isAutoRotationDisabled = disable; this.pausePlayButtonNode.hidden = disable; -} +}; /* Public function to update controls/caption styling */ -CarouselPreviousNext.prototype.setAccessibleStyling = function(accessible) { +CarouselPreviousNext.prototype.setAccessibleStyling = function (accessible) { if (accessible) { this.domNode.classList.add('carousel-moreaccessible'); - } - else { + } else { this.domNode.classList.remove('carousel-moreaccessible'); } -} +}; CarouselPreviousNext.prototype.showCarouselItem = function (index) { - this.currentIndex = index; - for(var i = 0; i < this.carouselItemNodes.length; i++) { + for (var i = 0; i < this.carouselItemNodes.length; i++) { var carouselItemNode = this.carouselItemNodes[i]; if (index === i) { carouselItemNode.classList.add('active'); - } - else { + } else { carouselItemNode.classList.remove('active'); } } -} +}; CarouselPreviousNext.prototype.previousCarouselItem = function () { var nextIndex = this.currentIndex - 1; @@ -134,7 +165,7 @@ CarouselPreviousNext.prototype.previousCarouselItem = function () { nextIndex = this.carouselItemNodes.length - 1; } this.showCarouselItem(nextIndex); -} +}; CarouselPreviousNext.prototype.nextCarouselItem = function () { var nextIndex = this.currentIndex + 1; @@ -142,20 +173,23 @@ CarouselPreviousNext.prototype.nextCarouselItem = function () { nextIndex = 0; } this.showCarouselItem(nextIndex); -} +}; CarouselPreviousNext.prototype.rotateSlides = function () { - if (!this.isAutoRotationDisabled ) { - if ((!this.hasFocus && - !this.hasHover && - this.isPlayingEnabled) || - this.hasUserActivatedPlay) { + if (!this.isAutoRotationDisabled) { + if ( + (!this.hasFocus && !this.hasHover && this.isPlayingEnabled) || + this.hasUserActivatedPlay + ) { this.nextCarouselItem(); } } - this.slideTimeout = setTimeout(this.rotateSlides.bind(this), this.timeInterval); -} + this.slideTimeout = setTimeout( + this.rotateSlides.bind(this), + this.timeInterval + ); +}; CarouselPreviousNext.prototype.updatePlaying = function (play) { this.isPlayingEnabled = play; @@ -165,122 +199,129 @@ CarouselPreviousNext.prototype.updatePlaying = function (play) { this.pausePlayButtonNode.classList.remove('play'); this.pausePlayButtonNode.classList.add('pause'); this.liveRegionNode.setAttribute('aria-live', 'off'); - } - else { + } else { this.pausePlayButtonNode.setAttribute('aria-label', this.playLabel); this.pausePlayButtonNode.classList.remove('pause'); this.pausePlayButtonNode.classList.add('play'); this.liveRegionNode.setAttribute('aria-live', 'polite'); } -} +}; - /* Event Handlers */ +/* Event Handlers */ CarouselPreviousNext.prototype.handleImageLinkFocus = function () { this.liveRegionNode.classList.add('focus'); -} +}; CarouselPreviousNext.prototype.handleImageLinkBlur = function () { this.liveRegionNode.classList.remove('focus'); -} +}; CarouselPreviousNext.prototype.handleMouseOver = function (event) { if (!this.pausePlayButtonNode.contains(event.target)) { this.hasHover = true; } -} +}; CarouselPreviousNext.prototype.handleMouseOut = function () { this.hasHover = false; -} +}; - /* EVENT HANDLERS */ +/* EVENT HANDLERS */ CarouselPreviousNext.prototype.handlePausePlayButtonClick = function () { this.hasUserActivatedPlay = !this.isPlayingEnabled; this.updatePlaying(!this.isPlayingEnabled); -} +}; CarouselPreviousNext.prototype.handlePreviousButtonClick = function () { this.previousCarouselItem(); -} +}; CarouselPreviousNext.prototype.handleNextButtonClick = function () { this.nextCarouselItem(); -} +}; - /* Event Handlers for carousel items*/ +/* Event Handlers for carousel items*/ CarouselPreviousNext.prototype.handleFocusIn = function () { this.liveRegionNode.setAttribute('aria-live', 'polite'); this.hasFocus = true; -} +}; CarouselPreviousNext.prototype.handleFocusOut = function () { if (this.isPlayingEnabled) { this.liveRegionNode.setAttribute('aria-live', 'off'); } this.hasFocus = false; -} +}; /* Initialize Carousel and options */ -window.addEventListener('load', function () { - var carouselEls = document.querySelectorAll('.carousel'); - var carousels = []; - - // set example behavior based on - // default setting of the checkboxes and the parameters in the URL - // update checkboxes based on any corresponding URL parameters - var checkboxes = document.querySelectorAll('.carousel-options input[type=checkbox]'); - var urlParams = new URLSearchParams(location.search); - var carouselOptions = {}; - - // initialize example features based on - // default setting of the checkboxes and the parameters in the URL - // update checkboxes based on any corresponding URL parameters - checkboxes.forEach(function(checkbox) { - var checked = checkbox.checked; - - if (urlParams.has(checkbox.value)) { - var urlParam = urlParams.get(checkbox.value); - if (typeof urlParam === 'string') { - checked = urlParam === 'true'; - checkbox.checked = checked; +window.addEventListener( + 'load', + function () { + var carouselEls = document.querySelectorAll('.carousel'); + var carousels = []; + + // set example behavior based on + // default setting of the checkboxes and the parameters in the URL + // update checkboxes based on any corresponding URL parameters + var checkboxes = document.querySelectorAll( + '.carousel-options input[type=checkbox]' + ); + var urlParams = new URLSearchParams(location.search); + var carouselOptions = {}; + + // initialize example features based on + // default setting of the checkboxes and the parameters in the URL + // update checkboxes based on any corresponding URL parameters + checkboxes.forEach(function (checkbox) { + var checked = checkbox.checked; + + if (urlParams.has(checkbox.value)) { + var urlParam = urlParams.get(checkbox.value); + if (typeof urlParam === 'string') { + checked = urlParam === 'true'; + checkbox.checked = checked; + } } - } - carouselOptions[checkbox.value] = checkbox.checked; - }); - - carouselEls.forEach(function (node) { - carousels.push(new CarouselPreviousNext(node, carouselOptions)); - }); - - // add change event to checkboxes - checkboxes.forEach(function(checkbox) { - var updateEvent; - switch(checkbox.value) { - case 'moreaccessible': - updateEvent = 'setAccessibleStyling'; - break; - case 'norotate': - updateEvent = 'enableOrDisableAutoRotation'; - break; - } - - // update the carousel behavior and URL when a checkbox state changes - checkbox.addEventListener('change', function(event) { - urlParams.set(event.target.value, event.target.checked + ''); - window.history.replaceState(null, '', window.location.pathname + '?' + urlParams); + carouselOptions[checkbox.value] = checkbox.checked; + }); - if (updateEvent) { - carousels.forEach(function (carousel) { - carousel[updateEvent](event.target.checked); - }); - } + carouselEls.forEach(function (node) { + carousels.push(new CarouselPreviousNext(node, carouselOptions)); }); - }); -}, false); + // add change event to checkboxes + checkboxes.forEach(function (checkbox) { + var updateEvent; + switch (checkbox.value) { + case 'moreaccessible': + updateEvent = 'setAccessibleStyling'; + break; + case 'norotate': + updateEvent = 'enableOrDisableAutoRotation'; + break; + } + // update the carousel behavior and URL when a checkbox state changes + checkbox.addEventListener('change', function (event) { + urlParams.set(event.target.value, event.target.checked + ''); + window.history.replaceState( + null, + '', + window.location.pathname + '?' + urlParams + ); + + if (updateEvent) { + carousels.forEach(function (carousel) { + carousel[updateEvent](event.target.checked); + }); + } + }); + }); + }, + false +); diff --git a/examples/carousel/js/carousel-tablist.js b/examples/carousel/js/carousel-tablist.js index b6ab7bdf49..70b8620c0d 100644 --- a/examples/carousel/js/carousel-tablist.js +++ b/examples/carousel/js/carousel-tablist.js @@ -1,9 +1,9 @@ /* -* File: carousel-tablist.js -* -* Desc: Carousel Tablist widget that implements ARIA Authoring Practices -* -*/ + * File: carousel-tablist.js + * + * Desc: Carousel Tablist widget that implements ARIA Authoring Practices + * + */ 'use strict'; @@ -12,10 +12,13 @@ var CarouselTablist = function (node, options) { // merge passed options with defaults - options = Object.assign({ moreaccessible: false, paused: false, norotate: false }, (options || {})); + options = Object.assign( + { moreaccessible: false, paused: false, norotate: false }, + options || {} + ); // a prefers-reduced-motion user setting must always override autoplay - var hasReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); + var hasReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); if (hasReducedMotion.matches) { options.paused = true; } @@ -30,14 +33,16 @@ var CarouselTablist = function (node, options) { this.tabpanelNodes = []; this.liveRegionNode = node.querySelector('.carousel-items'); - this.pausePlayButtonNode = document.querySelector('.carousel-tablist .controls button.rotation'); + this.pausePlayButtonNode = document.querySelector( + '.carousel-tablist .controls button.rotation' + ); this.playLabel = 'Start automatic slide show'; this.pauseLabel = 'Stop automatic slide show'; /* State properties */ this.hasUserActivatedPlay = false; // set when the user activates the play/pause button - this.isAutoRotationDisabled = options.norotate // This property for disabling auto rotation + this.isAutoRotationDisabled = options.norotate; // This property for disabling auto rotation this.isPlayingEnabled = !options.paused; // This property is also set in updatePlaying method this.timeInterval = 5000; // length of slide rotation in ms this.currentIndex = 0; // index of current slide @@ -65,24 +70,35 @@ var CarouselTablist = function (node, options) { this.tabpanelNodes.push(tabpanelNode); // support stopping rotation when any element receives focus in the tabpanel - tabpanelNode.addEventListener('focusin', this.handleTabpanelFocusIn.bind(this)); - tabpanelNode.addEventListener('focusout', this.handleTabpanelFocusOut.bind(this)); + tabpanelNode.addEventListener( + 'focusin', + this.handleTabpanelFocusIn.bind(this) + ); + tabpanelNode.addEventListener( + 'focusout', + this.handleTabpanelFocusOut.bind(this) + ); var imageLink = tabpanelNode.querySelector('.carousel-image a'); if (imageLink) { - imageLink.addEventListener('focus', this.handleImageLinkFocus.bind(this)); + imageLink.addEventListener( + 'focus', + this.handleImageLinkFocus.bind(this) + ); imageLink.addEventListener('blur', this.handleImageLinkBlur.bind(this)); } - } - else { + } else { this.tabpanelNodes.push(null); } } // Pause Button if (this.pausePlayButtonNode) { - this.pausePlayButtonNode.addEventListener('click', this.handlePausePlayButtonClick.bind(this)); + this.pausePlayButtonNode.addEventListener( + 'click', + this.handlePausePlayButtonClick.bind(this) + ); } // Handle hover events @@ -95,23 +111,22 @@ var CarouselTablist = function (node, options) { this.updatePlaying(!options.paused && !options.norotate); this.setAccessibleStyling(options.moreaccessible); this.rotateSlides(); -} +}; /* Public function to disable/enable rotation and if false, hide pause/play button*/ -CarouselTablist.prototype.enableOrDisableAutoRotation = function(disable) { +CarouselTablist.prototype.enableOrDisableAutoRotation = function (disable) { this.isAutoRotationDisabled = disable; this.pausePlayButtonNode.hidden = disable; -} +}; /* Public function to update controls/caption styling */ -CarouselTablist.prototype.setAccessibleStyling = function(accessible) { +CarouselTablist.prototype.setAccessibleStyling = function (accessible) { if (accessible) { this.domNode.classList.add('carousel-tablist-moreaccessible'); - } - else { + } else { this.domNode.classList.remove('carousel-tablist-moreaccessible'); } -} +}; CarouselTablist.prototype.hideTabpanel = function (index) { var tabNode = this.tabNodes[index]; @@ -123,7 +138,7 @@ CarouselTablist.prototype.hideTabpanel = function (index) { if (panelNode) { panelNode.classList.remove('active'); } -} +}; CarouselTablist.prototype.showTabpanel = function (index, moveFocus) { var tabNode = this.tabNodes[index]; @@ -139,7 +154,7 @@ CarouselTablist.prototype.showTabpanel = function (index, moveFocus) { if (moveFocus) { tabNode.focus(); } -} +}; CarouselTablist.prototype.setSelectedTab = function (index, moveFocus) { if (index === this.currentIndex) { @@ -152,7 +167,7 @@ CarouselTablist.prototype.setSelectedTab = function (index, moveFocus) { } this.showTabpanel(index, moveFocus); -} +}; CarouselTablist.prototype.setSelectedToPreviousTab = function (moveFocus) { var nextIndex = this.currentIndex - 1; @@ -162,7 +177,7 @@ CarouselTablist.prototype.setSelectedToPreviousTab = function (moveFocus) { } this.setSelectedTab(nextIndex, moveFocus); -} +}; CarouselTablist.prototype.setSelectedToNextTab = function (moveFocus) { var nextIndex = this.currentIndex + 1; @@ -172,20 +187,23 @@ CarouselTablist.prototype.setSelectedToNextTab = function (moveFocus) { } this.setSelectedTab(nextIndex, moveFocus); -} +}; CarouselTablist.prototype.rotateSlides = function () { - if (!this.isAutoRotationDisabled ) { - if ((!this.hasFocus && - !this.hasHover && - this.isPlayingEnabled) || - this.hasUserActivatedPlay) { + if (!this.isAutoRotationDisabled) { + if ( + (!this.hasFocus && !this.hasHover && this.isPlayingEnabled) || + this.hasUserActivatedPlay + ) { this.setSelectedToNextTab(false); } } - this.slideTimeout = setTimeout(this.rotateSlides.bind(this), this.timeInterval); -} + this.slideTimeout = setTimeout( + this.rotateSlides.bind(this), + this.timeInterval + ); +}; CarouselTablist.prototype.updatePlaying = function (play) { this.isPlayingEnabled = play; @@ -195,49 +213,47 @@ CarouselTablist.prototype.updatePlaying = function (play) { this.pausePlayButtonNode.classList.remove('play'); this.pausePlayButtonNode.classList.add('pause'); this.liveRegionNode.setAttribute('aria-live', 'off'); - } - else { + } else { this.pausePlayButtonNode.setAttribute('aria-label', this.playLabel); this.pausePlayButtonNode.classList.remove('pause'); this.pausePlayButtonNode.classList.add('play'); this.liveRegionNode.setAttribute('aria-live', 'polite'); } -} +}; - /* Event Handlers */ +/* Event Handlers */ CarouselTablist.prototype.handleImageLinkFocus = function () { this.liveRegionNode.classList.add('focus'); -} +}; CarouselTablist.prototype.handleImageLinkBlur = function () { this.liveRegionNode.classList.remove('focus'); -} +}; CarouselTablist.prototype.handleMouseOver = function (event) { if (!this.pausePlayButtonNode.contains(event.target)) { this.hasHover = true; } -} +}; CarouselTablist.prototype.handleMouseOut = function () { this.hasHover = false; -} +}; - /* EVENT HANDLERS */ +/* EVENT HANDLERS */ CarouselTablist.prototype.handlePausePlayButtonClick = function () { this.hasUserActivatedPlay = !this.isPlayingEnabled; this.updatePlaying(!this.isPlayingEnabled); -} +}; - /* Event Handlers for Tabs*/ +/* Event Handlers for Tabs*/ CarouselTablist.prototype.handleTabKeydown = function (event) { var flag = false; switch (event.key) { - case 'ArrowRight': this.setSelectedToNextTab(true); flag = true; @@ -266,18 +282,18 @@ CarouselTablist.prototype.handleTabKeydown = function (event) { event.stopPropagation(); event.preventDefault(); } -} +}; CarouselTablist.prototype.handleTabClick = function (event) { var index = this.tabNodes.indexOf(event.currentTarget); this.setSelectedTab(index, true); -} +}; CarouselTablist.prototype.handleTabFocus = function () { this.tablistNode.classList.add('focus'); this.liveRegionNode.setAttribute('aria-live', 'polite'); this.hasFocus = true; -} +}; CarouselTablist.prototype.handleTabBlur = function () { this.tablistNode.classList.remove('focus'); @@ -286,75 +302,84 @@ CarouselTablist.prototype.handleTabBlur = function () { } this.hasFocus = false; -} +}; - /* Event Handlers for Tabpanels*/ +/* Event Handlers for Tabpanels*/ CarouselTablist.prototype.handleTabpanelFocusIn = function () { this.hasFocus = true; -} +}; CarouselTablist.prototype.handleTabpanelFocusOut = function () { this.hasFocus = false; -} +}; /* Iniitalize Carousel Tablists and options */ -window.addEventListener('load', function () { - var carouselEls = document.querySelectorAll('.carousel-tablist'); - var carousels = []; - - // set example behavior based on - // default setting of the checkboxes and the parameters in the URL - // update checkboxes based on any corresponding URL parameters - var checkboxes = document.querySelectorAll('.carousel-options input[type=checkbox]'); - var urlParams = new URLSearchParams(location.search); - var carouselOptions = {}; - - // initialize example features based on - // default setting of the checkboxes and the parameters in the URL - // update checkboxes based on any corresponding URL parameters - checkboxes.forEach(function(checkbox) { - var checked = checkbox.checked; - - if (urlParams.has(checkbox.value)) { - var urlParam = urlParams.get(checkbox.value); - if (typeof urlParam === 'string') { - checked = urlParam === 'true'; - checkbox.checked = checked; +window.addEventListener( + 'load', + function () { + var carouselEls = document.querySelectorAll('.carousel-tablist'); + var carousels = []; + + // set example behavior based on + // default setting of the checkboxes and the parameters in the URL + // update checkboxes based on any corresponding URL parameters + var checkboxes = document.querySelectorAll( + '.carousel-options input[type=checkbox]' + ); + var urlParams = new URLSearchParams(location.search); + var carouselOptions = {}; + + // initialize example features based on + // default setting of the checkboxes and the parameters in the URL + // update checkboxes based on any corresponding URL parameters + checkboxes.forEach(function (checkbox) { + var checked = checkbox.checked; + + if (urlParams.has(checkbox.value)) { + var urlParam = urlParams.get(checkbox.value); + if (typeof urlParam === 'string') { + checked = urlParam === 'true'; + checkbox.checked = checked; + } } - } - carouselOptions[checkbox.value] = checkbox.checked; - }); - - carouselEls.forEach(function (node) { - carousels.push(new CarouselTablist(node, carouselOptions)); - }); - - // add change event to checkboxes - checkboxes.forEach(function(checkbox) { - var updateEvent; - switch(checkbox.value) { - case 'moreaccessible': - updateEvent = 'setAccessibleStyling'; - break; - case 'norotate': - updateEvent = 'enableOrDisableAutoRotation'; - break; - } + carouselOptions[checkbox.value] = checkbox.checked; + }); - // update the carousel behavior and URL when a checkbox state changes - checkbox.addEventListener('change', function(event) { - urlParams.set(event.target.value, event.target.checked + ''); - window.history.replaceState(null, '', window.location.pathname + '?' + urlParams); + carouselEls.forEach(function (node) { + carousels.push(new CarouselTablist(node, carouselOptions)); + }); - if (updateEvent) { - carousels.forEach(function (carousel) { - carousel[updateEvent](event.target.checked); - }); + // add change event to checkboxes + checkboxes.forEach(function (checkbox) { + var updateEvent; + switch (checkbox.value) { + case 'moreaccessible': + updateEvent = 'setAccessibleStyling'; + break; + case 'norotate': + updateEvent = 'enableOrDisableAutoRotation'; + break; } - }); - }); -}, false); + // update the carousel behavior and URL when a checkbox state changes + checkbox.addEventListener('change', function (event) { + urlParams.set(event.target.value, event.target.checked + ''); + window.history.replaceState( + null, + '', + window.location.pathname + '?' + urlParams + ); + + if (updateEvent) { + carousels.forEach(function (carousel) { + carousel[updateEvent](event.target.checked); + }); + } + }); + }); + }, + false +); diff --git a/examples/checkbox/checkbox-1/js/checkbox.js b/examples/checkbox/checkbox-1/js/checkbox.js index ca5e7269c8..092727e974 100644 --- a/examples/checkbox/checkbox-1/js/checkbox.js +++ b/examples/checkbox/checkbox-1/js/checkbox.js @@ -1,28 +1,27 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: Checkbox.js -* -* Desc: Checkbox widget that implements ARIA Authoring Practices -* for a menu of links -* -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: Checkbox.js + * + * Desc: Checkbox widget that implements ARIA Authoring Practices + * for a menu of links + * + */ 'use strict'; /* -* @constructor Checkbox -* -* -*/ + * @constructor Checkbox + * + * + */ var Checkbox = function (domNode) { - this.domNode = domNode; this.keyCode = Object.freeze({ - 'RETURN': 13, - 'SPACE': 32 + RETURN: 13, + SPACE: 32, }); }; @@ -33,22 +32,18 @@ Checkbox.prototype.init = function () { this.domNode.setAttribute('aria-checked', 'false'); } - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); }; Checkbox.prototype.toggleCheckbox = function () { - if (this.domNode.getAttribute('aria-checked') === 'true') { this.domNode.setAttribute('aria-checked', 'false'); - } - else { + } else { this.domNode.setAttribute('aria-checked', 'true'); } - }; /* EVENT HANDLERS */ @@ -83,4 +78,3 @@ Checkbox.prototype.handleFocus = function (event) { Checkbox.prototype.handleBlur = function (event) { this.domNode.classList.remove('focus'); }; - diff --git a/examples/checkbox/checkbox-2/js/checkboxMixed.js b/examples/checkbox/checkbox-2/js/checkboxMixed.js index 752f37b32f..d2ca7f9a8f 100644 --- a/examples/checkbox/checkbox-2/js/checkboxMixed.js +++ b/examples/checkbox/checkbox-2/js/checkboxMixed.js @@ -1,29 +1,28 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: CheckboxMixed.js -* -* Desc: CheckboxMixed widget that implements ARIA Authoring Practices -* for a menu of links -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: CheckboxMixed.js + * + * Desc: CheckboxMixed widget that implements ARIA Authoring Practices + * for a menu of links + */ 'use strict'; /* -* @constructor CheckboxMixed -* -* -*/ + * @constructor CheckboxMixed + * + * + */ var CheckboxMixed = function (domNode) { - this.domNode = domNode; this.controlledCheckboxes = []; this.keyCode = Object.freeze({ - 'RETURN': 13, - 'SPACE': 32 + RETURN: 13, + SPACE: 32, }); }; @@ -39,17 +38,15 @@ CheckboxMixed.prototype.init = function () { this.controlledCheckboxes.push(ccb); } - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); this.updateCheckboxMixed(); - }; CheckboxMixed.prototype.updateCheckboxMixed = function () { - var count = 0; for (var i = 0; i < this.controlledCheckboxes.length; i++) { @@ -60,12 +57,10 @@ CheckboxMixed.prototype.updateCheckboxMixed = function () { if (count === 0) { this.domNode.setAttribute('aria-checked', 'false'); - } - else { + } else { if (count === this.controlledCheckboxes.length) { this.domNode.setAttribute('aria-checked', 'true'); - } - else { + } else { this.domNode.setAttribute('aria-checked', 'mixed'); this.updateControlledStates(); } @@ -74,12 +69,13 @@ CheckboxMixed.prototype.updateCheckboxMixed = function () { CheckboxMixed.prototype.updateControlledStates = function () { for (var i = 0; i < this.controlledCheckboxes.length; i++) { - this.controlledCheckboxes[i].lastState = this.controlledCheckboxes[i].isChecked(); + this.controlledCheckboxes[i].lastState = this.controlledCheckboxes[ + i + ].isChecked(); } }; CheckboxMixed.prototype.anyLastChecked = function () { - var count = 0; for (var i = 0; i < this.controlledCheckboxes.length; i++) { @@ -89,36 +85,29 @@ CheckboxMixed.prototype.anyLastChecked = function () { } return count > 0; - }; CheckboxMixed.prototype.setControlledCheckboxes = function (value) { - for (var i = 0; i < this.controlledCheckboxes.length; i++) { this.controlledCheckboxes[i].setChecked(value); } this.updateCheckboxMixed(); - }; CheckboxMixed.prototype.toggleCheckboxMixed = function () { - var state = this.domNode.getAttribute('aria-checked'); if (state === 'false') { if (this.anyLastChecked()) { this.setControlledCheckboxes('last'); - } - else { + } else { this.setControlledCheckboxes('true'); } - } - else { + } else { if (state === 'mixed') { this.setControlledCheckboxes('true'); - } - else { + } else { this.setControlledCheckboxes('false'); } } @@ -158,4 +147,3 @@ CheckboxMixed.prototype.handleFocus = function (event) { CheckboxMixed.prototype.handleBlur = function (event) { this.domNode.classList.remove('focus'); }; - diff --git a/examples/checkbox/checkbox-2/js/controlledCheckbox.js b/examples/checkbox/checkbox-2/js/controlledCheckbox.js index 866ccbf3c5..b44c95eeba 100644 --- a/examples/checkbox/checkbox-2/js/controlledCheckbox.js +++ b/examples/checkbox/checkbox-2/js/controlledCheckbox.js @@ -1,39 +1,35 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: controlledCheckbox.js -* -* Desc: ControlledCheckbox widget that implements ARIA Authoring Practices -* for a mixed checkbox -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: controlledCheckbox.js + * + * Desc: ControlledCheckbox widget that implements ARIA Authoring Practices + * for a mixed checkbox + */ 'use strict'; /* -* @constructor ControlledCheckbox -* -* -*/ + * @constructor ControlledCheckbox + * + * + */ var ControlledCheckbox = function (domNode, controllerObj) { - this.domNode = domNode; this.controller = controllerObj; this.lastState = false; - }; ControlledCheckbox.prototype.init = function () { - this.lastState = this.isChecked(); console.log(this.lastState); this.domNode.addEventListener('change', this.handleChange.bind(this)); - this.domNode.addEventListener('keydown', this.handleKeyup.bind(this), true); - this.domNode.addEventListener('click', this.handleClick.bind(this), true); - + this.domNode.addEventListener('keydown', this.handleKeyup.bind(this), true); + this.domNode.addEventListener('click', this.handleClick.bind(this), true); }; ControlledCheckbox.prototype.isChecked = function () { @@ -49,7 +45,6 @@ ControlledCheckbox.prototype.isChecked = function () { ControlledCheckbox.prototype.setChecked = function (value) { // if standard input[type=checkbox] if (typeof this.domNode.checked === 'boolean') { - switch (value) { case 'true': this.domNode.checked = true; @@ -70,7 +65,6 @@ ControlledCheckbox.prototype.setChecked = function (value) { // If ARIA checkbox widget if (typeof this.domNode.getAttribute('aria-checked') === 'string') { - switch (value) { case 'true': case 'false': @@ -80,8 +74,7 @@ ControlledCheckbox.prototype.setChecked = function (value) { case 'last': if (this.lastState) { this.domNode.setAttribute('aria-checked', 'true'); - } - else { + } else { this.domNode.setAttribute('aria-checked', 'false'); } break; @@ -105,4 +98,3 @@ ControlledCheckbox.prototype.handleKeyup = function (event) { ControlledCheckbox.prototype.handleClick = function (event) { this.lastState = this.isChecked(); }; - diff --git a/examples/combobox/js/combobox-autocomplete.js b/examples/combobox/js/combobox-autocomplete.js index 20fe7fcb03..4e965fcf32 100644 --- a/examples/combobox/js/combobox-autocomplete.js +++ b/examples/combobox/js/combobox-autocomplete.js @@ -1,15 +1,14 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; var ComboboxAutocomplete = function (comboboxNode, buttonNode, listboxNode) { - this.comboboxNode = comboboxNode; - this.buttonNode = buttonNode; - this.listboxNode = listboxNode; + this.buttonNode = buttonNode; + this.listboxNode = listboxNode; this.comboboxHasVisualFocus = false; this.listboxHasVisualFocus = false; @@ -22,39 +21,58 @@ var ComboboxAutocomplete = function (comboboxNode, buttonNode, listboxNode) { this.allOptions = []; - this.option = null; - this.firstOption = null; - this.lastOption = null; + this.option = null; + this.firstOption = null; + this.lastOption = null; this.filteredOptions = []; - this.filter = ''; + this.filter = ''; }; ComboboxAutocomplete.prototype.init = function () { - var autocomplete = this.comboboxNode.getAttribute('aria-autocomplete'); if (typeof autocomplete === 'string') { autocomplete = autocomplete.toLowerCase(); - this.isNone = autocomplete === 'none'; - this.isList = autocomplete === 'list'; - this.isBoth = autocomplete === 'both'; - } - else { + this.isNone = autocomplete === 'none'; + this.isList = autocomplete === 'list'; + this.isBoth = autocomplete === 'both'; + } else { // default value of autocomplete this.isNone = true; } - this.comboboxNode.addEventListener('keydown', this.handleComboboxKeyDown.bind(this)); - this.comboboxNode.addEventListener('keyup', this.handleComboboxKeyUp.bind(this)); - this.comboboxNode.addEventListener('click', this.handleComboboxClick.bind(this)); - this.comboboxNode.addEventListener('focus', this.handleComboboxFocus.bind(this)); - this.comboboxNode.addEventListener('blur', this.handleComboboxBlur.bind(this)); + this.comboboxNode.addEventListener( + 'keydown', + this.handleComboboxKeyDown.bind(this) + ); + this.comboboxNode.addEventListener( + 'keyup', + this.handleComboboxKeyUp.bind(this) + ); + this.comboboxNode.addEventListener( + 'click', + this.handleComboboxClick.bind(this) + ); + this.comboboxNode.addEventListener( + 'focus', + this.handleComboboxFocus.bind(this) + ); + this.comboboxNode.addEventListener( + 'blur', + this.handleComboboxBlur.bind(this) + ); // initialize pop up menu - this.listboxNode.addEventListener('mouseover', this.handleListboxMouseover.bind(this)); - this.listboxNode.addEventListener('mouseout', this.handleListboxMouseout.bind(this)); + this.listboxNode.addEventListener( + 'mouseover', + this.handleListboxMouseover.bind(this) + ); + this.listboxNode.addEventListener( + 'mouseout', + this.handleListboxMouseout.bind(this) + ); // Traverse the element children of domNode: configure each with // option role behavior and store reference in.options array. @@ -64,10 +82,9 @@ ComboboxAutocomplete.prototype.init = function () { var node = nodes[i]; this.allOptions.push(node); - node.addEventListener('click', this.handleOptionClick.bind(this)); - node.addEventListener('mouseover', this.handleOptionMouseover.bind(this)); - node.addEventListener('mouseout', this.handleOptionMouseout.bind(this)); - + node.addEventListener('click', this.handleOptionClick.bind(this)); + node.addEventListener('mouseover', this.handleOptionMouseover.bind(this)); + node.addEventListener('mouseout', this.handleOptionMouseout.bind(this)); } this.filterOptions(); @@ -79,18 +96,16 @@ ComboboxAutocomplete.prototype.init = function () { if (button && button.tagName === 'BUTTON') { button.addEventListener('click', this.handleButtonClick.bind(this)); } - }; ComboboxAutocomplete.prototype.getLowercaseContent = function (node) { return node.textContent.toLowerCase(); -} +}; ComboboxAutocomplete.prototype.setActiveDescendant = function (option) { if (option && this.listboxHasVisualFocus) { this.comboboxNode.setAttribute('aria-activedescendant', option.id); - } - else { + } else { this.comboboxNode.setAttribute('aria-activedescendant', ''); } }; @@ -98,7 +113,7 @@ ComboboxAutocomplete.prototype.setActiveDescendant = function (option) { ComboboxAutocomplete.prototype.setValue = function (value) { this.filter = value; this.comboboxNode.value = this.filter; - this.comboboxNode.setSelectionRange(this.filter.length,this.filter.length); + this.comboboxNode.setSelectionRange(this.filter.length, this.filter.length); this.filterOptions(); }; @@ -113,12 +128,17 @@ ComboboxAutocomplete.prototype.setOption = function (option, flag) { this.setActiveDescendant(this.option); if (this.isBoth) { - this.comboboxNode.value = this.option.textContent; + this.comboboxNode.value = this.option.textContent; if (flag) { - this.comboboxNode.setSelectionRange(this.option.textContent.length,this.option.textContent.length); - } - else { - this.comboboxNode.setSelectionRange(this.filter.length,this.option.textContent.length); + this.comboboxNode.setSelectionRange( + this.option.textContent.length, + this.option.textContent.length + ); + } else { + this.comboboxNode.setSelectionRange( + this.filter.length, + this.option.textContent.length + ); } } } @@ -152,7 +172,6 @@ ComboboxAutocomplete.prototype.removeVisualFocusAll = function () { // ComboboxAutocomplete Events ComboboxAutocomplete.prototype.filterOptions = function () { - // do not filter any options if autocomplete is none if (this.isNone) { this.filter = ''; @@ -162,12 +181,15 @@ ComboboxAutocomplete.prototype.filterOptions = function () { var currentOption = this.option; var filter = this.filter.toLowerCase(); - this.filteredOptions = []; + this.filteredOptions = []; this.listboxNode.innerHTML = ''; for (var i = 0; i < this.allOptions.length; i++) { option = this.allOptions[i]; - if (filter.length === 0 || this.getLowercaseContent(option).indexOf(filter) === 0) { + if ( + filter.length === 0 || + this.getLowercaseContent(option).indexOf(filter) === 0 + ) { this.filteredOptions.push(option); this.listboxNode.appendChild(option); } @@ -177,38 +199,37 @@ ComboboxAutocomplete.prototype.filterOptions = function () { var numItems = this.filteredOptions.length; if (numItems > 0) { this.firstOption = this.filteredOptions[0]; - this.lastOption = this.filteredOptions[numItems - 1]; + this.lastOption = this.filteredOptions[numItems - 1]; if (currentOption && this.filteredOptions.indexOf(currentOption) >= 0) { option = currentOption; - } - else { + } else { option = this.firstOption; } - } - else { + } else { this.firstOption = null; option = null; - this.lastOption = null; + this.lastOption = null; } return option; }; ComboboxAutocomplete.prototype.setCurrentOptionStyle = function (option) { - for (var i = 0; i < this.filteredOptions.length; i++) { var opt = this.filteredOptions[i]; if (opt === option) { opt.setAttribute('aria-selected', 'true'); - if ((this.listboxNode.scrollTop + this.listboxNode.offsetHeight) < (opt.offsetTop + opt.offsetHeight)) { - this.listboxNode.scrollTop = opt.offsetTop + opt.offsetHeight - this.listboxNode.offsetHeight; - } - else if (this.listboxNode.scrollTop > (opt.offsetTop + 2)) { - this.listboxNode.scrollTop = opt.offsetTop; + if ( + this.listboxNode.scrollTop + this.listboxNode.offsetHeight < + opt.offsetTop + opt.offsetHeight + ) { + this.listboxNode.scrollTop = + opt.offsetTop + opt.offsetHeight - this.listboxNode.offsetHeight; + } else if (this.listboxNode.scrollTop > opt.offsetTop + 2) { + this.listboxNode.scrollTop = opt.offsetTop; } - } - else { + } else { opt.removeAttribute('aria-selected'); } } @@ -259,7 +280,12 @@ ComboboxAutocomplete.prototype.close = function (force) { force = false; } - if (force || (!this.comboboxHasVisualFocus && !this.listboxHasVisualFocus && !this.hasHover)) { + if ( + force || + (!this.comboboxHasVisualFocus && + !this.listboxHasVisualFocus && + !this.hasHover) + ) { this.setCurrentOptionStyle(false); this.listboxNode.style.display = 'none'; this.comboboxNode.setAttribute('aria-expanded', 'false'); @@ -273,15 +299,14 @@ ComboboxAutocomplete.prototype.close = function (force) { ComboboxAutocomplete.prototype.handleComboboxKeyDown = function (event) { var flag = false, char = event.key, - altKey = event.altKey; + altKey = event.altKey; if (event.ctrlKey || event.shiftKey) { return; } switch (event.key) { - - case "Enter": + case 'Enter': if (this.listboxHasVisualFocus) { this.setValue(this.option.textContent); } @@ -290,19 +315,20 @@ ComboboxAutocomplete.prototype.handleComboboxKeyDown = function (event) { flag = true; break; - case "Down": - case "ArrowDown": + case 'Down': + case 'ArrowDown': if (this.filteredOptions.length > 0) { if (altKey) { this.open(); - } - else { + } else { this.open(); - if (this.listboxHasVisualFocus || (this.isBoth && this.filteredOptions.length > 1)) { + if ( + this.listboxHasVisualFocus || + (this.isBoth && this.filteredOptions.length > 1) + ) { this.setOption(this.getNextOption(this.option), true); this.setVisualFocusListbox(); - } - else { + } else { this.setOption(this.firstOption, true); this.setVisualFocusListbox(); } @@ -311,14 +337,12 @@ ComboboxAutocomplete.prototype.handleComboboxKeyDown = function (event) { flag = true; break; - case "Up": - case "ArrowUp": - + case 'Up': + case 'ArrowUp': if (this.hasOptions()) { if (this.listboxHasVisualFocus) { this.setOption(this.getPreviousOption(this.option), true); - } - else { + } else { this.open(); if (!altKey) { this.setOption(this.lastOption, true); @@ -329,15 +353,14 @@ ComboboxAutocomplete.prototype.handleComboboxKeyDown = function (event) { flag = true; break; - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': if (this.isOpen()) { this.close(true); this.filter = this.comboboxNode.value; this.filterOptions(); this.setVisualFocusCombobox(); - } - else { + } else { this.setValue(''); this.comboboxNode.value = ''; } @@ -345,7 +368,7 @@ ComboboxAutocomplete.prototype.handleComboboxKeyDown = function (event) { flag = true; break; - case "Tab": + case 'Tab': this.close(true); if (this.listboxHasVisualFocus) { if (this.option) { @@ -354,18 +377,17 @@ ComboboxAutocomplete.prototype.handleComboboxKeyDown = function (event) { } break; - case "Home": - this.comboboxNode.setSelectionRange(0,0); + case 'Home': + this.comboboxNode.setSelectionRange(0, 0); flag = true; break; - case "End": + case 'End': var length = this.comboboxNode.value.length; - this.comboboxNode.setSelectionRange(length,length); + this.comboboxNode.setSelectionRange(length, length); flag = true; break; - default: break; } @@ -374,12 +396,11 @@ ComboboxAutocomplete.prototype.handleComboboxKeyDown = function (event) { event.stopPropagation(); event.preventDefault(); } - }; ComboboxAutocomplete.prototype.isPrintableCharacter = function (str) { return str.length === 1 && str.match(/\S/); -} +}; ComboboxAutocomplete.prototype.handleComboboxKeyUp = function (event) { var flag = false, @@ -394,34 +415,32 @@ ComboboxAutocomplete.prototype.handleComboboxKeyUp = function (event) { if (this.comboboxNode.value.length < this.filter.length) { this.filter = this.comboboxNode.value; this.option = null; - this.filterOptions() + this.filterOptions(); } - if (event.key === "Escape" || event.key === "Esc") { + if (event.key === 'Escape' || event.key === 'Esc') { return; } switch (event.key) { - - case "Backspace": + case 'Backspace': this.setVisualFocusCombobox(); this.setCurrentOptionStyle(false); this.filter = this.comboboxNode.value; this.option = null; - this.filterOptions() + this.filterOptions(); flag = true; break; - case "Left": - case "ArrowLeft": - case "Right": - case "ArrowRight": - case "Home": - case "End": + case 'Left': + case 'ArrowLeft': + case 'Right': + case 'ArrowRight': + case 'Home': + case 'End': if (this.isBoth) { this.filter = this.comboboxNode.value; - } - else { + } else { this.option = null; this.setCurrentOptionStyle(false); } @@ -442,7 +461,11 @@ ComboboxAutocomplete.prototype.handleComboboxKeyUp = function (event) { this.open(); } - if (this.getLowercaseContent(option).indexOf(this.comboboxNode.value.toLowerCase()) === 0) { + if ( + this.getLowercaseContent(option).indexOf( + this.comboboxNode.value.toLowerCase() + ) === 0 + ) { this.option = option; if (this.isBoth || this.listboxHasVisualFocus) { this.setCurrentOptionStyle(option); @@ -450,19 +473,16 @@ ComboboxAutocomplete.prototype.handleComboboxKeyUp = function (event) { this.setOption(option); } } - } - else { + } else { this.option = null; this.setCurrentOptionStyle(false); } - } - else { + } else { this.close(); this.option = null; this.setActiveDescendant(false); } - } - else if (this.comboboxNode.value.length) { + } else if (this.comboboxNode.value.length) { this.open(); } } @@ -474,14 +494,12 @@ ComboboxAutocomplete.prototype.handleComboboxKeyUp = function (event) { event.stopPropagation(); event.preventDefault(); } - }; ComboboxAutocomplete.prototype.handleComboboxClick = function (event) { if (this.isOpen()) { this.close(true); - } - else { + } else { this.open(); } }; @@ -504,15 +522,13 @@ ComboboxAutocomplete.prototype.handleComboboxBlur = function (event) { ComboboxAutocomplete.prototype.handleButtonClick = function (event) { if (this.isOpen()) { this.close(true); - } - else { + } else { this.open(); } this.comboboxNode.focus(); this.setVisualFocusCombobox(); }; - /* Listbox Events */ ComboboxAutocomplete.prototype.handleListboxMouseover = function (event) { @@ -534,7 +550,6 @@ ComboboxAutocomplete.prototype.handleOptionClick = function (event) { ComboboxAutocomplete.prototype.handleOptionMouseover = function (event) { this.hasHover = true; this.open(); - }; ComboboxAutocomplete.prototype.handleOptionMouseout = function (event) { @@ -542,20 +557,17 @@ ComboboxAutocomplete.prototype.handleOptionMouseout = function (event) { setTimeout(this.close.bind(this, false), 300); }; - // Initialize comboboxes window.addEventListener('load', function () { - var comboboxes = document.querySelectorAll('.combobox-list'); for (var i = 0; i < comboboxes.length; i++) { var combobox = comboboxes[i]; var comboboxNode = combobox.querySelector('input'); - var buttonNode = combobox.querySelector('button'); - var listboxNode = combobox.querySelector('[role="listbox"]'); + var buttonNode = combobox.querySelector('button'); + var listboxNode = combobox.querySelector('[role="listbox"]'); var cba = new ComboboxAutocomplete(comboboxNode, buttonNode, listboxNode); cba.init(); } - }); diff --git a/examples/combobox/js/combobox-datepicker.js b/examples/combobox/js/combobox-datepicker.js index ab6cb9fade..c791141ea3 100644 --- a/examples/combobox/js/combobox-datepicker.js +++ b/examples/combobox/js/combobox-datepicker.js @@ -1,33 +1,48 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: ComboboxDatePicker.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: ComboboxDatePicker.js + */ 'use strict'; var ComboboxDatePicker = function (cdp) { this.buttonLabel = 'Date'; - this.monthLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + this.monthLabels = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; this.messageCursorKeys = 'Cursor keys can navigate dates'; this.lastMessage = ''; - this.comboboxNode = cdp.querySelector('input[type="text"]'); - this.buttonNode = cdp.querySelector('.group button'); - this.dialogNode = cdp.querySelector('[role="dialog"]'); - this.messageNode = this.dialogNode.querySelector('.dialog-message'); + this.comboboxNode = cdp.querySelector('input[type="text"]'); + this.buttonNode = cdp.querySelector('.group button'); + this.dialogNode = cdp.querySelector('[role="dialog"]'); + this.messageNode = this.dialogNode.querySelector('.dialog-message'); this.monthYearNode = this.dialogNode.querySelector('.month-year'); - this.prevYearNode = this.dialogNode.querySelector('.prev-year'); + this.prevYearNode = this.dialogNode.querySelector('.prev-year'); this.prevMonthNode = this.dialogNode.querySelector('.prev-month'); this.nextMonthNode = this.dialogNode.querySelector('.next-month'); - this.nextYearNode = this.dialogNode.querySelector('.next-year'); + this.nextYearNode = this.dialogNode.querySelector('.next-year'); - this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]'); - this.cancelButtonNode = this.dialogNode.querySelector('button[value="cancel"]'); + this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]'); + this.cancelButtonNode = this.dialogNode.querySelector( + 'button[value="cancel"]' + ); this.tbodyNode = this.dialogNode.querySelector('table.dates tbody'); @@ -36,39 +51,86 @@ var ComboboxDatePicker = function (cdp) { this.days = []; this.focusDay = new Date(); - this.selectedDay = new Date(0,0,1); + this.selectedDay = new Date(0, 0, 1); this.isMouseDownOnBackground = false; - }; ComboboxDatePicker.prototype.init = function () { - - this.comboboxNode.addEventListener('keydown', this.handleComboboxKeyDown.bind(this)); - this.comboboxNode.addEventListener('click', this.handleComboboxClick.bind(this)); - this.comboboxNode.addEventListener('focus', this.handleComboboxFocus.bind(this)); - this.comboboxNode.addEventListener('blur', this.handleComboboxBlur.bind(this)); - - this.buttonNode.addEventListener('keydown', this.handleButtonKeyDown.bind(this)); - this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); + this.comboboxNode.addEventListener( + 'keydown', + this.handleComboboxKeyDown.bind(this) + ); + this.comboboxNode.addEventListener( + 'click', + this.handleComboboxClick.bind(this) + ); + this.comboboxNode.addEventListener( + 'focus', + this.handleComboboxFocus.bind(this) + ); + this.comboboxNode.addEventListener( + 'blur', + this.handleComboboxBlur.bind(this) + ); + + this.buttonNode.addEventListener( + 'keydown', + this.handleButtonKeyDown.bind(this) + ); + this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); this.okButtonNode.addEventListener('click', this.handleOkButton.bind(this)); this.okButtonNode.addEventListener('keydown', this.handleOkButton.bind(this)); - this.cancelButtonNode.addEventListener('click', this.handleCancelButton.bind(this)); - this.cancelButtonNode.addEventListener('keydown', this.handleCancelButton.bind(this)); - - this.prevMonthNode.addEventListener('click', this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('click', this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('click', this.handlePreviousYearButton.bind(this)); - this.nextYearNode.addEventListener('click', this.handleNextYearButton.bind(this)); - - this.prevMonthNode.addEventListener('keydown', this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('keydown', this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('keydown', this.handlePreviousYearButton.bind(this)); - this.nextYearNode.addEventListener('keydown', this.handleNextYearButton.bind(this)); - - document.body.addEventListener('mouseup', this.handleBackgroundMouseUp.bind(this), true); + this.cancelButtonNode.addEventListener( + 'click', + this.handleCancelButton.bind(this) + ); + this.cancelButtonNode.addEventListener( + 'keydown', + this.handleCancelButton.bind(this) + ); + + this.prevMonthNode.addEventListener( + 'click', + this.handlePreviousMonthButton.bind(this) + ); + this.nextMonthNode.addEventListener( + 'click', + this.handleNextMonthButton.bind(this) + ); + this.prevYearNode.addEventListener( + 'click', + this.handlePreviousYearButton.bind(this) + ); + this.nextYearNode.addEventListener( + 'click', + this.handleNextYearButton.bind(this) + ); + + this.prevMonthNode.addEventListener( + 'keydown', + this.handlePreviousMonthButton.bind(this) + ); + this.nextMonthNode.addEventListener( + 'keydown', + this.handleNextMonthButton.bind(this) + ); + this.prevYearNode.addEventListener( + 'keydown', + this.handlePreviousYearButton.bind(this) + ); + this.nextYearNode.addEventListener( + 'keydown', + this.handleNextYearButton.bind(this) + ); + + document.body.addEventListener( + 'mouseup', + this.handleBackgroundMouseUp.bind(this), + true + ); // Create Grid of Dates @@ -96,22 +158,26 @@ ComboboxDatePicker.prototype.init = function () { }; ComboboxDatePicker.prototype.isSameDay = function (day1, day2) { - return (day1.getFullYear() == day2.getFullYear()) && - (day1.getMonth() == day2.getMonth()) && - (day1.getDate() == day2.getDate()); + return ( + day1.getFullYear() == day2.getFullYear() && + day1.getMonth() == day2.getMonth() && + day1.getDate() == day2.getDate() + ); }; ComboboxDatePicker.prototype.isNotSameMonth = function (day1, day2) { - return (day1.getFullYear() != day2.getFullYear()) || - (day1.getMonth() != day2.getMonth()); + return ( + day1.getFullYear() != day2.getFullYear() || + day1.getMonth() != day2.getMonth() + ); }; ComboboxDatePicker.prototype.updateGrid = function () { - var i, flag; var fd = this.focusDay; - this.monthYearNode.innerHTML = this.monthLabels[fd.getMonth()] + ' ' + fd.getFullYear(); + this.monthYearNode.innerHTML = + this.monthLabels[fd.getMonth()] + ' ' + fd.getFullYear(); var firstDayOfMonth = new Date(fd.getFullYear(), fd.getMonth(), 1); var dayOfWeek = firstDayOfMonth.getDay(); @@ -129,8 +195,7 @@ ComboboxDatePicker.prototype.updateGrid = function () { if (i === 35) { if (flag) { this.lastRowNode.style.visibility = 'hidden'; - } - else { + } else { this.lastRowNode.style.visibility = 'visible'; } } @@ -138,7 +203,6 @@ ComboboxDatePicker.prototype.updateGrid = function () { }; ComboboxDatePicker.prototype.setFocusDay = function (flag) { - if (typeof flag !== 'boolean') { flag = true; } @@ -146,8 +210,7 @@ ComboboxDatePicker.prototype.setFocusDay = function (flag) { var fd = this.focusDay; var getDayFromDataDateAttribute = this.getDayFromDataDateAttribute; - function checkDay (domNode) { - + function checkDay(domNode) { var d = getDayFromDataDateAttribute(domNode); domNode.setAttribute('tabindex', '-1'); @@ -159,9 +222,7 @@ ComboboxDatePicker.prototype.setFocusDay = function (flag) { } } - this.days.forEach(checkDay.bind(this)); - }; ComboboxDatePicker.prototype.open = function () { @@ -186,7 +247,7 @@ ComboboxDatePicker.prototype.close = function (flag) { this.setMessage(''); this.dialogNode.style.display = 'none'; - this.comboboxNode.setAttribute('aria-expanded', 'false') + this.comboboxNode.setAttribute('aria-expanded', 'false'); this.buttonNode.classList.remove('open'); if (flag) { @@ -199,24 +260,22 @@ ComboboxDatePicker.prototype.handleOkButton = function (event) { switch (event.type) { case 'keydown': - switch (event.key) { - case "Tab": + case 'Tab': if (!event.shiftKey) { this.prevYearNode.focus(); flag = true; } break; - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': this.close(); flag = true; break; default: break; - } break; @@ -241,18 +300,15 @@ ComboboxDatePicker.prototype.handleCancelButton = function (event) { switch (event.type) { case 'keydown': - switch (event.key) { - - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': this.close(); flag = true; break; default: break; - } break; @@ -275,17 +331,15 @@ ComboboxDatePicker.prototype.handleNextYearButton = function (event) { var flag = false; switch (event.type) { - case 'keydown': - switch (event.key) { - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': this.close(); flag = true; break; - case "Enter": + case 'Enter': this.moveToNextYear(); this.setFocusDay(false); flag = true; @@ -313,26 +367,23 @@ ComboboxDatePicker.prototype.handlePreviousYearButton = function (event) { var flag = false; switch (event.type) { - case 'keydown': - switch (event.key) { - - case "Enter": + case 'Enter': this.moveToPreviousYear(); this.setFocusDay(false); flag = true; break; - case "Tab": + case 'Tab': if (event.shiftKey) { this.okButtonNode.focus(); flag = true; } break; - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': this.close(); flag = true; break; @@ -362,17 +413,15 @@ ComboboxDatePicker.prototype.handleNextMonthButton = function (event) { var flag = false; switch (event.type) { - case 'keydown': - switch (event.key) { - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': this.close(); flag = true; break; - case "Enter": + case 'Enter': this.moveToNextMonth(); this.setFocusDay(false); flag = true; @@ -400,17 +449,15 @@ ComboboxDatePicker.prototype.handlePreviousMonthButton = function (event) { var flag = false; switch (event.type) { - case 'keydown': - switch (event.key) { - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': this.close(); flag = true; break; - case "Enter": + case 'Enter': this.moveToPreviousMonth(); this.setFocusDay(false); flag = true; @@ -440,14 +487,15 @@ ComboboxDatePicker.prototype.moveFocusToDay = function (day) { this.focusDay = day; - if ((d.getMonth() != this.focusDay.getMonth()) || - (d.getYear() != this.focusDay.getYear())) { + if ( + d.getMonth() != this.focusDay.getMonth() || + d.getYear() != this.focusDay.getYear() + ) { this.updateGrid(); } this.setFocusDay(); }; - ComboboxDatePicker.prototype.moveToNextYear = function () { this.focusDay.setFullYear(this.focusDay.getFullYear() + 1); this.updateGrid(); @@ -512,11 +560,15 @@ ComboboxDatePicker.prototype.isDayDisabled = function (domNode) { ComboboxDatePicker.prototype.getDayFromDataDateAttribute = function (domNode) { var parts = domNode.getAttribute('data-date').split('-'); - return new Date(parts[0], parseInt(parts[1])-1, parts[2]); + return new Date(parts[0], parseInt(parts[1]) - 1, parts[2]); }; -ComboboxDatePicker.prototype.updateDate = function (domNode, disable, day, selected) { - +ComboboxDatePicker.prototype.updateDate = function ( + domNode, + disable, + day, + selected +) { var d = day.getDate().toString(); if (day.getDate() <= 9) { d = '0' + d; @@ -534,8 +586,7 @@ ComboboxDatePicker.prototype.updateDate = function (domNode, disable, day, selec if (disable) { domNode.classList.add('disabled'); domNode.innerHTML = ''; - } - else { + } else { domNode.classList.remove('disabled'); domNode.innerHTML = day.getDate(); if (selected) { @@ -543,44 +594,40 @@ ComboboxDatePicker.prototype.updateDate = function (domNode, disable, day, selec domNode.setAttribute('tabindex', '0'); } } - }; ComboboxDatePicker.prototype.updateSelected = function (domNode) { for (var i = 0; i < this.days.length; i++) { var day = this.days[i]; - if (day === domNode) { + if (day === domNode) { day.setAttribute('aria-selected', 'true'); - } - else { + } else { day.removeAttribute('aria-selected'); } } }; - ComboboxDatePicker.prototype.handleDayKeyDown = function (event) { var flag = false; switch (event.key) { - - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': this.close(); break; - case " ": + case ' ': this.updateSelected(event.currentTarget); this.setComboboxDate(event.currentTarget); flag = true; break; - case "Enter": + case 'Enter': this.setComboboxDate(event.currentTarget); this.close(); break; - case "Tab": + case 'Tab': this.cancelButtonNode.focus(); if (event.shiftKey) { this.nextYearNode.focus(); @@ -589,58 +636,56 @@ ComboboxDatePicker.prototype.handleDayKeyDown = function (event) { flag = true; break; - case "Right": - case "ArrowRight": + case 'Right': + case 'ArrowRight': this.moveFocusToNextDay(); flag = true; break; - case "Left": - case "ArrowLeft": + case 'Left': + case 'ArrowLeft': this.moveFocusToPreviousDay(); flag = true; break; - case "Down": - case "ArrowDown": + case 'Down': + case 'ArrowDown': this.moveFocusToNextWeek(); flag = true; break; - case "Up": - case "ArrowUp": + case 'Up': + case 'ArrowUp': this.moveFocusToPreviousWeek(); flag = true; break; - case "PageUp": + case 'PageUp': if (event.shiftKey) { this.moveToPreviousYear(); - } - else { + } else { this.moveToPreviousMonth(); } this.setFocusDay(); flag = true; break; - case "PageDown": + case 'PageDown': if (event.shiftKey) { this.moveToNextYear(); - } - else { + } else { this.moveToNextMonth(); } this.setFocusDay(); flag = true; break; - case "Home": + case 'Home': this.moveFocusToFirstDayOfWeek(); flag = true; break; - case "End": + case 'End': this.moveFocusToLastDayOfWeek(); flag = true; break; @@ -653,7 +698,6 @@ ComboboxDatePicker.prototype.handleDayKeyDown = function (event) { }; ComboboxDatePicker.prototype.handleDayClick = function (event) { - if (!this.isDayDisabled(event.currentTarget)) { this.setComboboxDate(event.currentTarget); this.close(); @@ -661,7 +705,6 @@ ComboboxDatePicker.prototype.handleDayClick = function (event) { event.stopPropagation(); event.preventDefault(); - }; ComboboxDatePicker.prototype.handleDayFocus = function () { @@ -671,39 +714,40 @@ ComboboxDatePicker.prototype.handleDayFocus = function () { // Combobox methods ComboboxDatePicker.prototype.setComboboxDate = function (domNode) { - var d = this.focusDay; if (domNode) { d = this.getDayFromDataDateAttribute(domNode); } - this.comboboxNode.value = (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear(); - + this.comboboxNode.value = + d.getMonth() + 1 + '/' + d.getDate() + '/' + d.getFullYear(); }; ComboboxDatePicker.prototype.getDateFromCombobox = function () { - var parts = this.comboboxNode.value.split('/'); - if ((parts.length === 3) && - Number.isInteger(parseInt(parts[0])) && - Number.isInteger(parseInt(parts[1])) && - Number.isInteger(parseInt(parts[2]))) { - this.focusDay = new Date(parseInt(parts[2]), parseInt(parts[0]) - 1, parseInt(parts[1])); + if ( + parts.length === 3 && + Number.isInteger(parseInt(parts[0])) && + Number.isInteger(parseInt(parts[1])) && + Number.isInteger(parseInt(parts[2])) + ) { + this.focusDay = new Date( + parseInt(parts[2]), + parseInt(parts[0]) - 1, + parseInt(parts[1]) + ); this.selectedDay = new Date(this.focusDay); - } - else { + } else { // If not a valid date (MM/DD/YY) initialize with todays date this.focusDay = new Date(); - this.selectedDay = new Date(0,0,1); + this.selectedDay = new Date(0, 0, 1); } - }; ComboboxDatePicker.prototype.setMessage = function (str) { - - function setMessageDelayed () { + function setMessageDelayed() { this.messageNode.textContent = str; } @@ -716,38 +760,35 @@ ComboboxDatePicker.prototype.setMessage = function (str) { ComboboxDatePicker.prototype.handleComboboxKeyDown = function (event) { var flag = false, char = event.key, - altKey = event.altKey; + altKey = event.altKey; if (event.ctrlKey || event.shiftKey) { return; } switch (event.key) { - - case "Down": - case "ArrowDown": + case 'Down': + case 'ArrowDown': this.open(); this.setFocusDay(); flag = true; break; - case "Esc": - case "Escape": + case 'Esc': + case 'Escape': if (this.isOpen()) { this.close(false); - } - else { + } else { this.comboboxNode.value = ''; } this.option = null; flag = true; break; - case "Tab": + case 'Tab': this.close(false); break; - default: break; } @@ -756,14 +797,12 @@ ComboboxDatePicker.prototype.handleComboboxKeyDown = function (event) { event.stopPropagation(); event.preventDefault(); } - }; ComboboxDatePicker.prototype.handleComboboxClick = function (event) { if (this.isOpen()) { this.close(false); - } - else { + } else { this.open(); } @@ -780,8 +819,7 @@ ComboboxDatePicker.prototype.handleComboboxBlur = function (event) { }; ComboboxDatePicker.prototype.handleButtonKeyDown = function (event) { - - if (event.key === "Enter" || event.key === " ") { + if (event.key === 'Enter' || event.key === ' ') { this.open(); this.setFocusDay(); @@ -793,8 +831,7 @@ ComboboxDatePicker.prototype.handleButtonKeyDown = function (event) { ComboboxDatePicker.prototype.handleButtonClick = function (event) { if (this.isOpen()) { this.close(); - } - else { + } else { this.open(); this.setFocusDay(); } @@ -804,10 +841,11 @@ ComboboxDatePicker.prototype.handleButtonClick = function (event) { }; ComboboxDatePicker.prototype.handleBackgroundMouseUp = function (event) { - if (!this.comboboxNode.contains(event.target) && - !this.buttonNode.contains(event.target) && - !this.dialogNode.contains(event.target)) { - + if ( + !this.comboboxNode.contains(event.target) && + !this.buttonNode.contains(event.target) && + !this.dialogNode.contains(event.target) + ) { if (this.isOpen()) { this.close(false); event.stopPropagation(); @@ -818,13 +856,11 @@ ComboboxDatePicker.prototype.handleBackgroundMouseUp = function (event) { // Initialize menu button date picker -window.addEventListener('load' , function () { - +window.addEventListener('load', function () { var comboboxDatePickers = document.querySelectorAll('.combobox-datepicker'); comboboxDatePickers.forEach(function (dp) { var datePicker = new ComboboxDatePicker(dp); datePicker.init(); }); - }); diff --git a/examples/combobox/js/grid-combo-example.js b/examples/combobox/js/grid-combo-example.js index 24301ae8cd..b078ea3929 100644 --- a/examples/combobox/js/grid-combo-example.js +++ b/examples/combobox/js/grid-combo-example.js @@ -1,9 +1,9 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* ARIA Combobox Examples -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * ARIA Combobox Examples + */ 'use strict'; @@ -70,10 +70,10 @@ var FRUITS_AND_VEGGIES = [ ['Watercress', 'Vegetable'], ['Watermelon', 'Fruit'], ['Yam', 'Vegetable'], - ['Zucchini', 'Vegetable'] + ['Zucchini', 'Vegetable'], ]; -function searchVeggies (searchString) { +function searchVeggies(searchString) { var results = []; for (var i = 0; i < FRUITS_AND_VEGGIES.length; i++) { @@ -96,5 +96,4 @@ window.addEventListener('load', function () { document.getElementById('ex1-grid'), searchVeggies ); - }); diff --git a/examples/combobox/js/grid-combo.js b/examples/combobox/js/grid-combo.js index 74776fde34..78bc5c1078 100644 --- a/examples/combobox/js/grid-combo.js +++ b/examples/combobox/js/grid-combo.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -22,11 +22,7 @@ * The search function. The function accepts a search string and returns an * array of results. */ -aria.GridCombobox = function ( - input, - grid, - searchFn -) { +aria.GridCombobox = function (input, grid, searchFn) { this.input = input; this.grid = grid; this.searchFn = searchFn; @@ -78,7 +74,6 @@ aria.GridCombobox.prototype.handleInputKeyUp = function (evt) { } }; - aria.GridCombobox.prototype.handleInputKeyDown = function (evt) { var key = evt.which || evt.keyCode; var activeRowIndex = this.activeRowIndex; @@ -90,18 +85,17 @@ aria.GridCombobox.prototype.handleInputKeyDown = function (evt) { this.removeFocusCell(this.activeRowIndex, this.activeColIndex); this.activeRowIndex = -1; this.activeColIndex = 0; - this.input.setAttribute( - 'aria-activedescendant', - '' - ); - } - else { + this.input.setAttribute('aria-activedescendant', ''); + } else { if (!this.shown) { - setTimeout((function () { - // On Firefox, input does not get cleared here unless wrapped in - // a setTimeout - this.input.value = ''; - }).bind(this), 1); + setTimeout( + function () { + // On Firefox, input does not get cleared here unless wrapped in + // a setTimeout + this.input.value = ''; + }.bind(this), + 1 + ); } } if (this.shown) { @@ -132,8 +126,7 @@ aria.GridCombobox.prototype.handleInputKeyDown = function (evt) { if (activeColIndex <= 0) { activeColIndex = this.colsCount - 1; activeRowIndex = this.getRowIndex(key); - } - else { + } else { activeColIndex--; } if (this.gridFocused) { @@ -144,8 +137,7 @@ aria.GridCombobox.prototype.handleInputKeyDown = function (evt) { if (activeColIndex === -1 || activeColIndex >= this.colsCount - 1) { activeColIndex = 0; activeRowIndex = this.getRowIndex(key); - } - else { + } else { activeColIndex++; } if (this.gridFocused) { @@ -181,12 +173,8 @@ aria.GridCombobox.prototype.handleInputKeyDown = function (evt) { this.focusCell(activeRowIndex, activeColIndex); var selectedItem = this.getItemAt(activeRowIndex, this.selectionCol); selectedItem.setAttribute('aria-selected', 'true'); - } - else { - this.input.setAttribute( - 'aria-activedescendant', - '' - ); + } else { + this.input.setAttribute('aria-activedescendant', ''); } }; @@ -202,11 +190,9 @@ aria.GridCombobox.prototype.handleGridClick = function (evt) { var row; if (evt.target.getAttribute('role') === 'row') { row = evt.target; - } - else if (evt.target.getAttribute('role') === 'gridcell') { + } else if (evt.target.getAttribute('role') === 'gridcell') { row = evt.target.parentNode; - } - else { + } else { return; } @@ -256,8 +242,7 @@ aria.GridCombobox.prototype.getRowIndex = function (key) { case aria.KeyCode.LEFT: if (activeRowIndex <= 0) { activeRowIndex = this.rowsCount - 1; - } - else { + } else { activeRowIndex--; } break; @@ -265,8 +250,7 @@ aria.GridCombobox.prototype.getRowIndex = function (key) { case aria.KeyCode.RIGHT: if (activeRowIndex === -1 || activeRowIndex >= this.rowsCount - 1) { activeRowIndex = 0; - } - else { + } else { activeRowIndex++; } } @@ -274,12 +258,10 @@ aria.GridCombobox.prototype.getRowIndex = function (key) { return activeRowIndex; }; - aria.GridCombobox.prototype.getItemAt = function (rowIndex, colIndex) { return document.getElementById('result-item-' + rowIndex + 'x' + colIndex); }; - aria.GridCombobox.prototype.selectItem = function (item) { if (item) { this.input.value = item.innerText; @@ -297,10 +279,7 @@ aria.GridCombobox.prototype.hideResults = function () { this.input.setAttribute('aria-expanded', 'false'); this.rowsCount = 0; this.colsCount = 0; - this.input.setAttribute( - 'aria-activedescendant', - '' - ); + this.input.setAttribute('aria-activedescendant', ''); }; aria.GridCombobox.prototype.removeFocusCell = function (rowIndex, colIndex) { diff --git a/examples/combobox/js/select-only.js b/examples/combobox/js/select-only.js index 2c18abed80..f8441cfc51 100644 --- a/examples/combobox/js/select-only.js +++ b/examples/combobox/js/select-only.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -17,8 +17,8 @@ const SelectActions = { PageUp: 7, Previous: 8, Select: 9, - Type: 10 -} + Type: 10, +}; /* * Helper functions @@ -51,7 +51,11 @@ function getActionFromKey(event, menuOpen) { } // handle typing characters when open or closed - if (key === 'Backspace' || key === 'Clear' || (key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey)) { + if ( + key === 'Backspace' || + key === 'Clear' || + (key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey) + ) { return SelectActions.Type; } @@ -59,35 +63,32 @@ function getActionFromKey(event, menuOpen) { if (menuOpen) { if (key === 'ArrowUp' && altKey) { return SelectActions.CloseSelect; - } - else if (key === 'ArrowDown' && !altKey) { + } else if (key === 'ArrowDown' && !altKey) { return SelectActions.Next; - } - else if (key === 'ArrowUp') { + } else if (key === 'ArrowUp') { return SelectActions.Previous; - } - else if (key === 'PageUp') { + } else if (key === 'PageUp') { return SelectActions.PageUp; - } - else if (key === 'PageDown') { + } else if (key === 'PageDown') { return SelectActions.PageDown; - } - else if (key === 'Escape') { + } else if (key === 'Escape') { return SelectActions.Close; - } - else if (key === 'Enter' || key === ' ') { + } else if (key === 'Enter' || key === ' ') { return SelectActions.CloseSelect; } } } - + // return the index of an option from an array of options, based on a search string // if the filter is multiple iterations of the same letter (e.g "aaa"), then cycle through first-letter matches function getIndexByLetter(options, filter, startIndex = 0) { - const orderedOptions = [...options.slice(startIndex), ...options.slice(0, startIndex)]; + const orderedOptions = [ + ...options.slice(startIndex), + ...options.slice(0, startIndex), + ]; const firstMatch = filterOptions(orderedOptions, filter)[0]; const allSameLetter = (array) => array.every((letter) => letter === array[0]); - + // first check if there is an exact match for the typed string if (firstMatch) { return options.indexOf(firstMatch); @@ -104,12 +105,12 @@ function getIndexByLetter(options, filter, startIndex = 0) { return -1; } } - + // get an updated option index after performing an action function getUpdatedIndex(currentIndex, maxIndex, action) { const pageSize = 10; // used for pageup/pagedown - switch(action) { + switch (action) { case SelectActions.First: return 0; case SelectActions.Last: @@ -126,7 +127,7 @@ function getUpdatedIndex(currentIndex, maxIndex, action) { return currentIndex; } } - + // check if an element is currently scrollable function isScrollable(element) { return element && element.clientHeight < element.scrollHeight; @@ -139,12 +140,11 @@ function maintainScrollVisibility(activeElement, scrollParent) { const { offsetHeight: parentOffsetHeight, scrollTop } = scrollParent; const isAbove = offsetTop < scrollTop; - const isBelow = (offsetTop + offsetHeight) > (scrollTop + parentOffsetHeight); + const isBelow = offsetTop + offsetHeight > scrollTop + parentOffsetHeight; if (isAbove) { scrollParent.scrollTo(0, offsetTop); - } - else if (isBelow) { + } else if (isBelow) { scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight); } } @@ -153,7 +153,7 @@ function maintainScrollVisibility(activeElement, scrollParent) { * Select Component * Accepts a combobox element and an array of string options */ -const Select = function(el, options = []) { +const Select = function (el, options = []) { // element refs this.el = el; this.comboEl = el.querySelector('[role=combobox]'); @@ -172,10 +172,10 @@ const Select = function(el, options = []) { // init if (el && this.comboEl && this.listboxEl) { this.init(); - } -} + } +}; -Select.prototype.init = function() { +Select.prototype.init = function () { // select first option by default this.comboEl.innerHTML = this.options[0]; @@ -189,13 +189,14 @@ Select.prototype.init = function() { const optionEl = this.createOption(option, index); this.listboxEl.appendChild(optionEl); }); -} +}; -Select.prototype.createOption = function(optionText, index) { +Select.prototype.createOption = function (optionText, index) { const optionEl = document.createElement('div'); optionEl.setAttribute('role', 'option'); optionEl.id = `${this.idBase}-${index}`; - optionEl.className = index === 0 ? 'combo-option option-current' : 'combo-option'; + optionEl.className = + index === 0 ? 'combo-option option-current' : 'combo-option'; optionEl.setAttribute('aria-selected', `${index === 0}`); optionEl.innerText = optionText; @@ -206,9 +207,9 @@ Select.prototype.createOption = function(optionText, index) { optionEl.addEventListener('mousedown', this.onOptionMouseDown.bind(this)); return optionEl; -} +}; -Select.prototype.getSearchString = function(char) { +Select.prototype.getSearchString = function (char) { // reset typing timeout and start new timeout // this allows us to make multiple-letter matches, like a native select if (typeof this.searchTimeout === 'number') { @@ -218,13 +219,13 @@ Select.prototype.getSearchString = function(char) { this.searchTimeout = window.setTimeout(() => { this.searchString = ''; }, 500); - + // add most recent letter to saved search string this.searchString += char; return this.searchString; -} +}; -Select.prototype.onComboBlur = function() { +Select.prototype.onComboBlur = function () { // do not do blur action if ignoreBlur flag has been set if (this.ignoreBlur) { this.ignoreBlur = false; @@ -236,33 +237,35 @@ Select.prototype.onComboBlur = function() { this.selectOption(this.activeIndex); this.updateMenuState(false, false); } -} +}; -Select.prototype.onComboClick = function() { +Select.prototype.onComboClick = function () { this.updateMenuState(!this.open, false); -} +}; -Select.prototype.onComboKeyDown = function(event) { +Select.prototype.onComboKeyDown = function (event) { const { key } = event; const max = this.options.length - 1; const action = getActionFromKey(event, this.open); - switch(action) { + switch (action) { case SelectActions.Last: case SelectActions.First: this.updateMenuState(true); - // intentional fallthrough + // intentional fallthrough case SelectActions.Next: case SelectActions.Previous: case SelectActions.PageUp: case SelectActions.PageDown: event.preventDefault(); - return this.onOptionChange(getUpdatedIndex(this.activeIndex, max, action)); + return this.onOptionChange( + getUpdatedIndex(this.activeIndex, max, action) + ); case SelectActions.CloseSelect: event.preventDefault(); this.selectOption(this.activeIndex); - // intentional fallthrough + // intentional fallthrough case SelectActions.Close: event.preventDefault(); return this.updateMenuState(false); @@ -272,15 +275,19 @@ Select.prototype.onComboKeyDown = function(event) { event.preventDefault(); return this.updateMenuState(true); } -} +}; -Select.prototype.onComboType = function(letter) { +Select.prototype.onComboType = function (letter) { // open the listbox if it is closed this.updateMenuState(true); // find the index of the first matching option const searchString = this.getSearchString(letter); - const searchIndex = getIndexByLetter(this.options, searchString, this.activeIndex + 1); + const searchIndex = getIndexByLetter( + this.options, + searchString, + this.activeIndex + 1 + ); // if a match was found, go to it if (searchIndex >= 0) { @@ -291,9 +298,9 @@ Select.prototype.onComboType = function(letter) { window.clearTimeout(this.searchTimeout); this.searchString = ''; } -} +}; -Select.prototype.onOptionChange = function(index) { +Select.prototype.onOptionChange = function (index) { // update state this.activeIndex = index; @@ -311,21 +318,21 @@ Select.prototype.onOptionChange = function(index) { if (isScrollable(this.listboxEl)) { maintainScrollVisibility(options[index], this.listboxEl); } -} +}; -Select.prototype.onOptionClick = function(index) { +Select.prototype.onOptionClick = function (index) { this.onOptionChange(index); this.selectOption(index); this.updateMenuState(false); -} +}; -Select.prototype.onOptionMouseDown = function() { +Select.prototype.onOptionMouseDown = function () { // Clicking an option will cause a blur event, // but we don't want to perform the default keyboard blur action this.ignoreBlur = true; -} +}; -Select.prototype.selectOption = function(index) { +Select.prototype.selectOption = function (index) { // update state this.activeIndex = index; @@ -339,9 +346,9 @@ Select.prototype.selectOption = function(index) { optionEl.setAttribute('aria-selected', 'false'); }); options[index].setAttribute('aria-selected', 'true'); -} +}; -Select.prototype.updateMenuState = function(open, callFocus = true) { +Select.prototype.updateMenuState = function (open, callFocus = true) { if (this.open === open) { return; } @@ -352,21 +359,35 @@ Select.prototype.updateMenuState = function(open, callFocus = true) { // update aria-expanded and styles this.comboEl.setAttribute('aria-expanded', `${open}`); open ? this.el.classList.add('open') : this.el.classList.remove('open'); - + // update activedescendant const activeID = open ? `${this.idBase}-${this.activeIndex}` : ''; this.comboEl.setAttribute('aria-activedescendant', activeID); // move focus back to the combobox, if needed callFocus && this.comboEl.focus(); -} +}; // init select window.addEventListener('load', function () { - const options = ['Choose a Fruit', 'Apple', 'Banana', 'Blueberry', 'Boysenberry', 'Cherry', 'Cranberry', 'Durian', 'Eggplant', 'Fig', 'Grape', 'Guava', 'Huckleberry']; + const options = [ + 'Choose a Fruit', + 'Apple', + 'Banana', + 'Blueberry', + 'Boysenberry', + 'Cherry', + 'Cranberry', + 'Durian', + 'Eggplant', + 'Fig', + 'Grape', + 'Guava', + 'Huckleberry', + ]; const selectEls = document.querySelectorAll('.js-select'); selectEls.forEach((el) => { new Select(el, options); }); -}); \ No newline at end of file +}); diff --git a/examples/dialog-modal/js/alertdialog.js b/examples/dialog-modal/js/alertdialog.js index 59683874b3..18b1ed8c02 100644 --- a/examples/dialog-modal/js/alertdialog.js +++ b/examples/dialog-modal/js/alertdialog.js @@ -17,11 +17,15 @@ aria.Utils.triggerAlert = function (alertEl, content) { try { alertEl.textContent = content || null; alertEl.classList.remove('hidden'); - alertEl.addEventListener('transitionend', function (e) { - if (!this.classList.contains('active')) { - this.classList.add('hidden'); - } - }, true); + alertEl.addEventListener( + 'transitionend', + function (e) { + if (!this.classList.contains('active')) { + this.classList.add('hidden'); + } + }, + true + ); setTimeout(function () { alertEl.classList.add('active'); }, 1); @@ -29,14 +33,13 @@ aria.Utils.triggerAlert = function (alertEl, content) { alertEl.classList.remove('active'); resolve(); }, 3000); - } - catch (err) { + } catch (err) { reject(err); } }); }; -aria.Notes = function Notes (notesId, saveId, discardId, localStorageKey) { +aria.Notes = function Notes(notesId, saveId, discardId, localStorageKey) { this.notesInput = document.getElementById(notesId); this.saveBtn = document.getElementById(saveId); this.discardBtn = document.getElementById(discardId); @@ -45,13 +48,15 @@ aria.Notes = function Notes (notesId, saveId, discardId, localStorageKey) { Object.defineProperty(this, 'controls', { get: function () { - return document.querySelectorAll('[aria-controls=' + this.notesInput.id + ']'); - } + return document.querySelectorAll( + '[aria-controls=' + this.notesInput.id + ']' + ); + }, }); Object.defineProperty(this, 'hasContent', { get: function () { return this.notesInput.value.length > 0; - } + }, }); Object.defineProperty(this, 'savedValue', { get: function () { @@ -59,12 +64,12 @@ aria.Notes = function Notes (notesId, saveId, discardId, localStorageKey) { }, set: function (val) { this.save(val); - } + }, }); Object.defineProperty(this, 'isCurrent', { get: function () { return this.notesInput.value === this.savedValue; - } + }, }); Object.defineProperty(this, 'oninput', { get: function () { @@ -75,7 +80,7 @@ aria.Notes = function Notes (notesId, saveId, discardId, localStorageKey) { throw new TypeError('oninput must be a function'); } this.notesInput.addEventListener('input', fn); - } + }, }); if (this.saveBtn && this.discardBtn) { @@ -87,7 +92,10 @@ aria.Notes.prototype.save = function (val) { if (this.alert && !this.isCurrent) { aria.Utils.triggerAlert(this.alert, 'Saved'); } - localStorage.setItem(this.localStorageKey, JSON.stringify(val || this.notesInput.value)); + localStorage.setItem( + this.localStorageKey, + JSON.stringify(val || this.notesInput.value) + ); aria.Utils.disableCtrl(this.saveBtn); }; @@ -114,8 +122,7 @@ aria.Notes.prototype.enableCtrls = function () { aria.Notes.prototype.toggleCtrls = function () { if (this.hasContent) { this.enableCtrls(); - } - else { + } else { this.disableCtrls(); } }; @@ -124,16 +131,15 @@ aria.Notes.prototype.toggleCurrent = function () { if (!this.isCurrent) { this.notesInput.classList.remove('can-save'); aria.Utils.enableCtrl(this.saveBtn); - } - else { + } else { this.notesInput.classList.add('can-save'); aria.Utils.disableCtrl(this.saveBtn); } }; aria.Notes.prototype.keydownHandler = function (e) { - var mod = (navigator.userAgent.includes('Mac')) ? e.metaKey : e.ctrlKey; - if (e.key === 's' & mod) { + var mod = navigator.userAgent.includes('Mac') ? e.metaKey : e.ctrlKey; + if ((e.key === 's') & mod) { e.preventDefault(); this.save(); } @@ -153,7 +159,7 @@ aria.Notes.prototype.init = function () { }; /** initialization */ -document.addEventListener('DOMContentLoaded', function initAlertDialog () { +document.addEventListener('DOMContentLoaded', function initAlertDialog() { var notes = new aria.Notes('notes', 'notes_save', 'notes_confirm'); notes.alert = document.getElementById('alert_toast'); @@ -163,7 +169,9 @@ document.addEventListener('DOMContentLoaded', function initAlertDialog () { }; window.openAlertDialog = function (dialogId, triggerBtn, focusFirst) { - var target = document.getElementById(triggerBtn.getAttribute('aria-controls')); + var target = document.getElementById( + triggerBtn.getAttribute('aria-controls') + ); var dialog = document.getElementById(dialogId); var desc = document.getElementById(dialog.getAttribute('aria-describedby')); var wordCount = document.getElementById('word_count'); @@ -173,8 +181,8 @@ document.addEventListener('DOMContentLoaded', function initAlertDialog () { desc.appendChild(wordCount); } var count = target.value.split(/\s/).length; - var frag = (count > 1) ? 'words' : 'word'; - wordCount.textContent = count + ' ' + frag + ' will be deleted.'; + var frag = count > 1 ? 'words' : 'word'; + wordCount.textContent = count + ' ' + frag + ' will be deleted.'; openDialog(dialogId, target, focusFirst); }; }); diff --git a/examples/dialog-modal/js/datepicker-dialog.js b/examples/dialog-modal/js/datepicker-dialog.js index 6040886c4d..8470f3e5e1 100644 --- a/examples/dialog-modal/js/datepicker-dialog.js +++ b/examples/dialog-modal/js/datepicker-dialog.js @@ -1,774 +1,822 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: DatePickerDialog.js -*/ - -'use strict'; - -var DatePickerDialog = function (cdp) { - this.buttonLabelChoose = 'Choose Date'; - this.buttonLabelChange = 'Change Date'; - this.dayLabels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - this.monthLabels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - - this.messageCursorKeys = 'Cursor keys can navigate dates'; - this.lastMessage = ''; - - this.textboxNode = cdp.querySelector('input[type="text"'); - this.buttonNode = cdp.querySelector('.group button'); - this.dialogNode = cdp.querySelector('[role="dialog"]'); - this.messageNode = this.dialogNode.querySelector('.dialog-message'); - - this.monthYearNode = this.dialogNode.querySelector('.month-year'); - - this.prevYearNode = this.dialogNode.querySelector('.prev-year'); - this.prevMonthNode = this.dialogNode.querySelector('.prev-month'); - this.nextMonthNode = this.dialogNode.querySelector('.next-month'); - this.nextYearNode = this.dialogNode.querySelector('.next-year'); - - this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]'); - this.cancelButtonNode = this.dialogNode.querySelector('button[value="cancel"]'); - - this.tbodyNode = this.dialogNode.querySelector('table.dates tbody'); - - this.lastRowNode = null; - - this.days = []; - - this.focusDay = new Date(); - this.selectedDay = new Date(0,0,1); - - this.isMouseDownOnBackground = false; - - this.init(); - -}; - -DatePickerDialog.prototype.init = function () { - - this.textboxNode.addEventListener('blur', this.setDateForButtonLabel.bind(this)); - - this.buttonNode.addEventListener('keydown', this.handleButtonKeydown.bind(this)); - this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); - - this.okButtonNode.addEventListener('click', this.handleOkButton.bind(this)); - this.okButtonNode.addEventListener('keydown', this.handleOkButton.bind(this)); - - this.cancelButtonNode.addEventListener('click', this.handleCancelButton.bind(this)); - this.cancelButtonNode.addEventListener('keydown', this.handleCancelButton.bind(this)); - - this.prevMonthNode.addEventListener('click', this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('click', this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('click', this.handlePreviousYearButton.bind(this)); - this.nextYearNode.addEventListener('click', this.handleNextYearButton.bind(this)); - - this.prevMonthNode.addEventListener('keydown', this.handlePreviousMonthButton.bind(this)); - this.nextMonthNode.addEventListener('keydown', this.handleNextMonthButton.bind(this)); - this.prevYearNode.addEventListener('keydown', this.handlePreviousYearButton.bind(this)); - this.nextYearNode.addEventListener('keydown', this.handleNextYearButton.bind(this)); - - document.body.addEventListener('mouseup', this.handleBackgroundMouseUp.bind(this), true); - - // Create Grid of Dates - - this.tbodyNode.innerHTML = ''; - for (var i = 0; i < 6; i++) { - var row = this.tbodyNode.insertRow(i); - this.lastRowNode = row; - for (var j = 0; j < 7; j++) { - var cell = document.createElement('td'); - - cell.tabIndex = -1; - cell.addEventListener('click', this.handleDayClick.bind(this)); - cell.addEventListener('keydown', this.handleDayKeyDown.bind(this)); - cell.addEventListener('focus', this.handleDayFocus.bind(this)); - - cell.textContent = '-1'; - - row.appendChild(cell); - this.days.push(cell); - } - } - - this.updateGrid(); - this.close(false); - this.setDateForButtonLabel(); -}; - -DatePickerDialog.prototype.isSameDay = function (day1, day2) { - return (day1.getFullYear() == day2.getFullYear()) && - (day1.getMonth() == day2.getMonth()) && - (day1.getDate() == day2.getDate()); -}; - -DatePickerDialog.prototype.isNotSameMonth = function (day1, day2) { - return (day1.getFullYear() != day2.getFullYear()) || - (day1.getMonth() != day2.getMonth()); -}; - -DatePickerDialog.prototype.updateGrid = function () { - - var flag; - var fd = this.focusDay; - - this.monthYearNode.textContent = this.monthLabels[fd.getMonth()] + ' ' + fd.getFullYear(); - - var firstDayOfMonth = new Date(fd.getFullYear(), fd.getMonth(), 1); - var dayOfWeek = firstDayOfMonth.getDay(); - - firstDayOfMonth.setDate(firstDayOfMonth.getDate() - dayOfWeek); - - var d = new Date(firstDayOfMonth); - - for (var i = 0; i < this.days.length; i++) { - flag = d.getMonth() != fd.getMonth(); - this.updateDate(this.days[i], flag, d, this.isSameDay(d, this.selectedDay)); - d.setDate(d.getDate() + 1); - - // Hide last row if all dates are disabled (e.g. in next month) - if (i === 35) { - if (flag) { - this.lastRowNode.style.visibility = 'hidden'; - } - else { - this.lastRowNode.style.visibility = 'visible'; - } - } - } -}; - -DatePickerDialog.prototype.updateDate = function (domNode, disable, day, selected) { - - var d = day.getDate().toString(); - if (day.getDate() <= 9) { - d = '0' + d; - } - - var m = day.getMonth() + 1; - if (day.getMonth() < 9) { - m = '0' + m; - } - - domNode.tabIndex = -1; - domNode.removeAttribute('aria-selected'); - domNode.setAttribute('data-date', day.getFullYear() + '-' + m + '-' + d); - - if (disable) { - domNode.classList.add('disabled'); - domNode.textContent = ''; - } - else { - domNode.classList.remove('disabled'); - domNode.textContent = day.getDate(); - if (selected) { - domNode.setAttribute('aria-selected', 'true'); - domNode.tabIndex = 0; - } - } -}; - -DatePickerDialog.prototype.moveFocusToDay = function (day) { - var d = this.focusDay; - - this.focusDay = day; - - if ((d.getMonth() != this.focusDay.getMonth()) || - (d.getYear() != this.focusDay.getYear())) { - this.updateGrid(); - } - this.setFocusDay(); -}; - -DatePickerDialog.prototype.setFocusDay = function (flag) { - - if (typeof flag !== 'boolean') { - flag = true; - } - - for (var i = 0; i < this.days.length; i++) { - var dayNode = this.days[i]; - var day = this.getDayFromDataDateAttribute(dayNode); - - dayNode.tabIndex = -1; - if (this.isSameDay(day, this.focusDay)) { - dayNode.tabIndex = 0; - if (flag) { - dayNode.focus(); - } - } - } -}; - -DatePickerDialog.prototype.open = function () { - this.dialogNode.style.display = 'block'; - this.dialogNode.style.zIndex = 2; - - this.getDateFromTextbox(); - this.updateGrid(); -}; - -DatePickerDialog.prototype.isOpen = function () { - return window.getComputedStyle(this.dialogNode).display !== 'none'; -}; - -DatePickerDialog.prototype.close = function (flag) { - if (typeof flag !== 'boolean') { - // Default is to move focus to combobox - flag = true; - } - - this.setMessage(''); - this.dialogNode.style.display = 'none'; - - if (flag) { - this.buttonNode.focus(); - } -}; - -DatePickerDialog.prototype.moveToNextYear = function () { - this.focusDay.setFullYear(this.focusDay.getFullYear() + 1); - this.updateGrid(); -}; - -DatePickerDialog.prototype.moveToPreviousYear = function () { - this.focusDay.setFullYear(this.focusDay.getFullYear() - 1); - this.updateGrid(); -}; - -DatePickerDialog.prototype.moveToNextMonth = function () { - this.focusDay.setMonth(this.focusDay.getMonth() + 1); - this.updateGrid(); -}; - -DatePickerDialog.prototype.moveToPreviousMonth = function () { - this.focusDay.setMonth(this.focusDay.getMonth() - 1); - this.updateGrid(); -}; - -DatePickerDialog.prototype.moveFocusToNextDay = function () { - var d = new Date(this.focusDay); - d.setDate(d.getDate() + 1); - this.moveFocusToDay(d); -}; - -DatePickerDialog.prototype.moveFocusToNextWeek = function () { - var d = new Date(this.focusDay); - d.setDate(d.getDate() + 7); - this.moveFocusToDay(d); -}; - -DatePickerDialog.prototype.moveFocusToPreviousDay = function () { - var d = new Date(this.focusDay); - d.setDate(d.getDate() - 1); - this.moveFocusToDay(d); -}; - -DatePickerDialog.prototype.moveFocusToPreviousWeek = function () { - var d = new Date(this.focusDay); - d.setDate(d.getDate() - 7); - this.moveFocusToDay(d); -}; - -DatePickerDialog.prototype.moveFocusToFirstDayOfWeek = function () { - var d = new Date(this.focusDay); - d.setDate(d.getDate() - d.getDay()); - this.moveFocusToDay(d); -}; - -DatePickerDialog.prototype.moveFocusToLastDayOfWeek = function () { - var d = new Date(this.focusDay); - d.setDate(d.getDate() + (6 - d.getDay())); - this.moveFocusToDay(d); -}; - -// Day methods - -DatePickerDialog.prototype.isDayDisabled = function (domNode) { - return domNode.classList.contains('disabled'); -}; - -DatePickerDialog.prototype.getDayFromDataDateAttribute = function (domNode) { - var parts = domNode.getAttribute('data-date').split('-'); - return new Date(parts[0], parseInt(parts[1])-1, parts[2]); -}; -// Textbox methods - -DatePickerDialog.prototype.setTextboxDate = function (domNode) { - - var d = this.focusDay; - - if (domNode) { - d = this.getDayFromDataDateAttribute(domNode); - // updated aria-selected - this.days.forEach(day => day === domNode ? day.setAttribute('aria-selected', 'true') : day.removeAttribute('aria-selected')); - } - - this.textboxNode.value = (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear(); - this.setDateForButtonLabel(); - -}; - -DatePickerDialog.prototype.getDateFromTextbox = function () { - - var parts = this.textboxNode.value.split('/'); - var month = parseInt(parts[0]); - var day = parseInt(parts[1]); - var year = parseInt(parts[2]); - - if ((parts.length === 3) && - Number.isInteger(month) && - Number.isInteger(day) && - Number.isInteger(year)) { - if (year < 100) { - year = 2000 + year; - } - this.focusDay = new Date(year, month-1, day); - this.selectedDay = new Date(this.focusDay); - } - else { - // If not a valid date (MM/DD/YY) initialize with todays date - this.focusDay = new Date(); - this.selectedDay = new Date(0,0,1); - } - -}; - -DatePickerDialog.prototype.setDateForButtonLabel = function () { - - var parts = this.textboxNode.value.split('/'); - - if ((parts.length === 3) && - Number.isInteger(parseInt(parts[0])) && - Number.isInteger(parseInt(parts[1])) && - Number.isInteger(parseInt(parts[2]))) { - var day = new Date(parseInt(parts[2]), parseInt(parts[0]) - 1, parseInt(parts[1])); - - var label = this.buttonLabelChange; - label += ', ' + this.dayLabels[day.getDay()]; - label += ' ' + this.monthLabels[day.getMonth()]; - label += ' ' + (day.getDate()); - label += ', ' + day.getFullYear(); - this.buttonNode.setAttribute('aria-label', label); - } - else { - // If not a valid date, initialize with "Choose Date" - this.buttonNode.setAttribute('aria-label', this.buttonLabelChoose); - } -}; - -DatePickerDialog.prototype.setMessage = function (str) { - - function setMessageDelayed () { - this.messageNode.textContent = str; - } - - if (str !== this.lastMessage) { - setTimeout(setMessageDelayed.bind(this), 200); - this.lastMessage = str; - } -}; - -// Event handlers - - -DatePickerDialog.prototype.handleOkButton = function (event) { - var flag = false; - - switch (event.type) { - case 'keydown': - - switch (event.key) { - case "Tab": - if (!event.shiftKey) { - this.prevYearNode.focus(); - flag = true; - } - break; - - case "Esc": - case "Escape": - this.close(); - flag = true; - break; - - default: - break; - - } - break; - - case 'click': - this.setTextboxDate(); - this.close(); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePickerDialog.prototype.handleCancelButton = function (event) { - var flag = false; - - switch (event.type) { - case 'keydown': - - switch (event.key) { - - case "Esc": - case "Escape": - this.close(); - flag = true; - break; - - default: - break; - - } - break; - - case 'click': - this.close(); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePickerDialog.prototype.handleNextYearButton = function (event) { - var flag = false; - - switch (event.type) { - - case 'keydown': - - switch (event.key) { - case "Esc": - case "Escape": - this.close(); - flag = true; - break; - - case "Enter": - this.moveToNextYear(); - this.setFocusDay(false); - flag = true; - break; - } - - break; - - case 'click': - this.moveToNextYear(); - this.setFocusDay(false); - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePickerDialog.prototype.handlePreviousYearButton = function (event) { - var flag = false; - - switch (event.type) { - - case 'keydown': - - switch (event.key) { - - case "Enter": - this.moveToPreviousYear(); - this.setFocusDay(false); - flag = true; - break; - - case "Tab": - if (event.shiftKey) { - this.okButtonNode.focus(); - flag = true; - } - break; - - case "Esc": - case "Escape": - this.close(); - flag = true; - break; - - default: - break; - } - - break; - - case 'click': - this.moveToPreviousYear(); - this.setFocusDay(false); - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePickerDialog.prototype.handleNextMonthButton = function (event) { - var flag = false; - - switch (event.type) { - - case 'keydown': - - switch (event.key) { - case "Esc": - case "Escape": - this.close(); - flag = true; - break; - - case "Enter": - this.moveToNextMonth(); - this.setFocusDay(false); - flag = true; - break; - } - - break; - - case 'click': - this.moveToNextMonth(); - this.setFocusDay(false); - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePickerDialog.prototype.handlePreviousMonthButton = function (event) { - var flag = false; - - switch (event.type) { - - case 'keydown': - - switch (event.key) { - case "Esc": - case "Escape": - this.close(); - flag = true; - break; - - case "Enter": - this.moveToPreviousMonth(); - this.setFocusDay(false); - flag = true; - break; - } - - break; - - case 'click': - this.moveToPreviousMonth(); - this.setFocusDay(false); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePickerDialog.prototype.handleDayKeyDown = function (event) { - var flag = false; - - switch (event.key) { - - case "Esc": - case "Escape": - this.close(); - break; - - case " ": - this.setTextboxDate(event.currentTarget); - flag = true; - break; - - case "Enter": - this.setTextboxDate(event.currentTarget); - this.close(); - flag = true; - break; - - case "Tab": - this.cancelButtonNode.focus(); - if (event.shiftKey) { - this.nextYearNode.focus(); - } - this.setMessage(''); - flag = true; - break; - - case "Right": - case "ArrowRight": - this.moveFocusToNextDay(); - flag = true; - break; - - case "Left": - case "ArrowLeft": - this.moveFocusToPreviousDay(); - flag = true; - break; - - case "Down": - case "ArrowDown": - this.moveFocusToNextWeek(); - flag = true; - break; - - case "Up": - case "ArrowUp": - this.moveFocusToPreviousWeek(); - flag = true; - break; - - case "PageUp": - if (event.shiftKey) { - this.moveToPreviousYear(); - } - else { - this.moveToPreviousMonth(); - } - this.setFocusDay(); - flag = true; - break; - - case "PageDown": - if (event.shiftKey) { - this.moveToNextYear(); - } - else { - this.moveToNextMonth(); - } - this.setFocusDay(); - flag = true; - break; - - case "Home": - this.moveFocusToFirstDayOfWeek(); - flag = true; - break; - - case "End": - this.moveFocusToLastDayOfWeek(); - flag = true; - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -DatePickerDialog.prototype.handleDayClick = function (event) { - - if (!this.isDayDisabled(event.currentTarget)) { - this.setTextboxDate(event.currentTarget); - this.close(); - } - - event.stopPropagation(); - event.preventDefault(); - -}; - -DatePickerDialog.prototype.handleDayFocus = function () { - this.setMessage(this.messageCursorKeys); -}; - -DatePickerDialog.prototype.handleButtonKeydown = function (event) { - - if ((event.key === 'Enter') || - (event.key === ' ')) { - this.open(); - this.setFocusDay(); - - event.stopPropagation(); - event.preventDefault(); - } - -}; - -DatePickerDialog.prototype.handleButtonClick = function (event) { - if (this.isOpen()) { - this.close(); - } - else { - this.open(); - this.setFocusDay(); - } - - event.stopPropagation(); - event.preventDefault(); -}; - -DatePickerDialog.prototype.handleBackgroundMouseUp = function (event) { - if (!this.buttonNode.contains(event.target) && - !this.dialogNode.contains(event.target)) { - - if (this.isOpen()) { - this.close(false); - event.stopPropagation(); - event.preventDefault(); - } - } -}; - -// Initialize menu button date picker - -window.addEventListener('load' , function () { - - var datePickers = document.querySelectorAll('.datepicker'); - - datePickers.forEach(function (dp) { - new DatePickerDialog(dp); - }); - -}); +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: DatePickerDialog.js + */ + +'use strict'; + +var DatePickerDialog = function (cdp) { + this.buttonLabelChoose = 'Choose Date'; + this.buttonLabelChange = 'Change Date'; + this.dayLabels = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ]; + this.monthLabels = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + this.messageCursorKeys = 'Cursor keys can navigate dates'; + this.lastMessage = ''; + + this.textboxNode = cdp.querySelector('input[type="text"'); + this.buttonNode = cdp.querySelector('.group button'); + this.dialogNode = cdp.querySelector('[role="dialog"]'); + this.messageNode = this.dialogNode.querySelector('.dialog-message'); + + this.monthYearNode = this.dialogNode.querySelector('.month-year'); + + this.prevYearNode = this.dialogNode.querySelector('.prev-year'); + this.prevMonthNode = this.dialogNode.querySelector('.prev-month'); + this.nextMonthNode = this.dialogNode.querySelector('.next-month'); + this.nextYearNode = this.dialogNode.querySelector('.next-year'); + + this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]'); + this.cancelButtonNode = this.dialogNode.querySelector( + 'button[value="cancel"]' + ); + + this.tbodyNode = this.dialogNode.querySelector('table.dates tbody'); + + this.lastRowNode = null; + + this.days = []; + + this.focusDay = new Date(); + this.selectedDay = new Date(0, 0, 1); + + this.isMouseDownOnBackground = false; + + this.init(); +}; + +DatePickerDialog.prototype.init = function () { + this.textboxNode.addEventListener( + 'blur', + this.setDateForButtonLabel.bind(this) + ); + + this.buttonNode.addEventListener( + 'keydown', + this.handleButtonKeydown.bind(this) + ); + this.buttonNode.addEventListener('click', this.handleButtonClick.bind(this)); + + this.okButtonNode.addEventListener('click', this.handleOkButton.bind(this)); + this.okButtonNode.addEventListener('keydown', this.handleOkButton.bind(this)); + + this.cancelButtonNode.addEventListener( + 'click', + this.handleCancelButton.bind(this) + ); + this.cancelButtonNode.addEventListener( + 'keydown', + this.handleCancelButton.bind(this) + ); + + this.prevMonthNode.addEventListener( + 'click', + this.handlePreviousMonthButton.bind(this) + ); + this.nextMonthNode.addEventListener( + 'click', + this.handleNextMonthButton.bind(this) + ); + this.prevYearNode.addEventListener( + 'click', + this.handlePreviousYearButton.bind(this) + ); + this.nextYearNode.addEventListener( + 'click', + this.handleNextYearButton.bind(this) + ); + + this.prevMonthNode.addEventListener( + 'keydown', + this.handlePreviousMonthButton.bind(this) + ); + this.nextMonthNode.addEventListener( + 'keydown', + this.handleNextMonthButton.bind(this) + ); + this.prevYearNode.addEventListener( + 'keydown', + this.handlePreviousYearButton.bind(this) + ); + this.nextYearNode.addEventListener( + 'keydown', + this.handleNextYearButton.bind(this) + ); + + document.body.addEventListener( + 'mouseup', + this.handleBackgroundMouseUp.bind(this), + true + ); + + // Create Grid of Dates + + this.tbodyNode.innerHTML = ''; + for (var i = 0; i < 6; i++) { + var row = this.tbodyNode.insertRow(i); + this.lastRowNode = row; + for (var j = 0; j < 7; j++) { + var cell = document.createElement('td'); + + cell.tabIndex = -1; + cell.addEventListener('click', this.handleDayClick.bind(this)); + cell.addEventListener('keydown', this.handleDayKeyDown.bind(this)); + cell.addEventListener('focus', this.handleDayFocus.bind(this)); + + cell.textContent = '-1'; + + row.appendChild(cell); + this.days.push(cell); + } + } + + this.updateGrid(); + this.close(false); + this.setDateForButtonLabel(); +}; + +DatePickerDialog.prototype.isSameDay = function (day1, day2) { + return ( + day1.getFullYear() == day2.getFullYear() && + day1.getMonth() == day2.getMonth() && + day1.getDate() == day2.getDate() + ); +}; + +DatePickerDialog.prototype.isNotSameMonth = function (day1, day2) { + return ( + day1.getFullYear() != day2.getFullYear() || + day1.getMonth() != day2.getMonth() + ); +}; + +DatePickerDialog.prototype.updateGrid = function () { + var flag; + var fd = this.focusDay; + + this.monthYearNode.textContent = + this.monthLabels[fd.getMonth()] + ' ' + fd.getFullYear(); + + var firstDayOfMonth = new Date(fd.getFullYear(), fd.getMonth(), 1); + var dayOfWeek = firstDayOfMonth.getDay(); + + firstDayOfMonth.setDate(firstDayOfMonth.getDate() - dayOfWeek); + + var d = new Date(firstDayOfMonth); + + for (var i = 0; i < this.days.length; i++) { + flag = d.getMonth() != fd.getMonth(); + this.updateDate(this.days[i], flag, d, this.isSameDay(d, this.selectedDay)); + d.setDate(d.getDate() + 1); + + // Hide last row if all dates are disabled (e.g. in next month) + if (i === 35) { + if (flag) { + this.lastRowNode.style.visibility = 'hidden'; + } else { + this.lastRowNode.style.visibility = 'visible'; + } + } + } +}; + +DatePickerDialog.prototype.updateDate = function ( + domNode, + disable, + day, + selected +) { + var d = day.getDate().toString(); + if (day.getDate() <= 9) { + d = '0' + d; + } + + var m = day.getMonth() + 1; + if (day.getMonth() < 9) { + m = '0' + m; + } + + domNode.tabIndex = -1; + domNode.removeAttribute('aria-selected'); + domNode.setAttribute('data-date', day.getFullYear() + '-' + m + '-' + d); + + if (disable) { + domNode.classList.add('disabled'); + domNode.textContent = ''; + } else { + domNode.classList.remove('disabled'); + domNode.textContent = day.getDate(); + if (selected) { + domNode.setAttribute('aria-selected', 'true'); + domNode.tabIndex = 0; + } + } +}; + +DatePickerDialog.prototype.moveFocusToDay = function (day) { + var d = this.focusDay; + + this.focusDay = day; + + if ( + d.getMonth() != this.focusDay.getMonth() || + d.getYear() != this.focusDay.getYear() + ) { + this.updateGrid(); + } + this.setFocusDay(); +}; + +DatePickerDialog.prototype.setFocusDay = function (flag) { + if (typeof flag !== 'boolean') { + flag = true; + } + + for (var i = 0; i < this.days.length; i++) { + var dayNode = this.days[i]; + var day = this.getDayFromDataDateAttribute(dayNode); + + dayNode.tabIndex = -1; + if (this.isSameDay(day, this.focusDay)) { + dayNode.tabIndex = 0; + if (flag) { + dayNode.focus(); + } + } + } +}; + +DatePickerDialog.prototype.open = function () { + this.dialogNode.style.display = 'block'; + this.dialogNode.style.zIndex = 2; + + this.getDateFromTextbox(); + this.updateGrid(); +}; + +DatePickerDialog.prototype.isOpen = function () { + return window.getComputedStyle(this.dialogNode).display !== 'none'; +}; + +DatePickerDialog.prototype.close = function (flag) { + if (typeof flag !== 'boolean') { + // Default is to move focus to combobox + flag = true; + } + + this.setMessage(''); + this.dialogNode.style.display = 'none'; + + if (flag) { + this.buttonNode.focus(); + } +}; + +DatePickerDialog.prototype.moveToNextYear = function () { + this.focusDay.setFullYear(this.focusDay.getFullYear() + 1); + this.updateGrid(); +}; + +DatePickerDialog.prototype.moveToPreviousYear = function () { + this.focusDay.setFullYear(this.focusDay.getFullYear() - 1); + this.updateGrid(); +}; + +DatePickerDialog.prototype.moveToNextMonth = function () { + this.focusDay.setMonth(this.focusDay.getMonth() + 1); + this.updateGrid(); +}; + +DatePickerDialog.prototype.moveToPreviousMonth = function () { + this.focusDay.setMonth(this.focusDay.getMonth() - 1); + this.updateGrid(); +}; + +DatePickerDialog.prototype.moveFocusToNextDay = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() + 1); + this.moveFocusToDay(d); +}; + +DatePickerDialog.prototype.moveFocusToNextWeek = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() + 7); + this.moveFocusToDay(d); +}; + +DatePickerDialog.prototype.moveFocusToPreviousDay = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() - 1); + this.moveFocusToDay(d); +}; + +DatePickerDialog.prototype.moveFocusToPreviousWeek = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() - 7); + this.moveFocusToDay(d); +}; + +DatePickerDialog.prototype.moveFocusToFirstDayOfWeek = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() - d.getDay()); + this.moveFocusToDay(d); +}; + +DatePickerDialog.prototype.moveFocusToLastDayOfWeek = function () { + var d = new Date(this.focusDay); + d.setDate(d.getDate() + (6 - d.getDay())); + this.moveFocusToDay(d); +}; + +// Day methods + +DatePickerDialog.prototype.isDayDisabled = function (domNode) { + return domNode.classList.contains('disabled'); +}; + +DatePickerDialog.prototype.getDayFromDataDateAttribute = function (domNode) { + var parts = domNode.getAttribute('data-date').split('-'); + return new Date(parts[0], parseInt(parts[1]) - 1, parts[2]); +}; +// Textbox methods + +DatePickerDialog.prototype.setTextboxDate = function (domNode) { + var d = this.focusDay; + + if (domNode) { + d = this.getDayFromDataDateAttribute(domNode); + // updated aria-selected + this.days.forEach((day) => + day === domNode + ? day.setAttribute('aria-selected', 'true') + : day.removeAttribute('aria-selected') + ); + } + + this.textboxNode.value = + d.getMonth() + 1 + '/' + d.getDate() + '/' + d.getFullYear(); + this.setDateForButtonLabel(); +}; + +DatePickerDialog.prototype.getDateFromTextbox = function () { + var parts = this.textboxNode.value.split('/'); + var month = parseInt(parts[0]); + var day = parseInt(parts[1]); + var year = parseInt(parts[2]); + + if ( + parts.length === 3 && + Number.isInteger(month) && + Number.isInteger(day) && + Number.isInteger(year) + ) { + if (year < 100) { + year = 2000 + year; + } + this.focusDay = new Date(year, month - 1, day); + this.selectedDay = new Date(this.focusDay); + } else { + // If not a valid date (MM/DD/YY) initialize with todays date + this.focusDay = new Date(); + this.selectedDay = new Date(0, 0, 1); + } +}; + +DatePickerDialog.prototype.setDateForButtonLabel = function () { + var parts = this.textboxNode.value.split('/'); + + if ( + parts.length === 3 && + Number.isInteger(parseInt(parts[0])) && + Number.isInteger(parseInt(parts[1])) && + Number.isInteger(parseInt(parts[2])) + ) { + var day = new Date( + parseInt(parts[2]), + parseInt(parts[0]) - 1, + parseInt(parts[1]) + ); + + var label = this.buttonLabelChange; + label += ', ' + this.dayLabels[day.getDay()]; + label += ' ' + this.monthLabels[day.getMonth()]; + label += ' ' + day.getDate(); + label += ', ' + day.getFullYear(); + this.buttonNode.setAttribute('aria-label', label); + } else { + // If not a valid date, initialize with "Choose Date" + this.buttonNode.setAttribute('aria-label', this.buttonLabelChoose); + } +}; + +DatePickerDialog.prototype.setMessage = function (str) { + function setMessageDelayed() { + this.messageNode.textContent = str; + } + + if (str !== this.lastMessage) { + setTimeout(setMessageDelayed.bind(this), 200); + this.lastMessage = str; + } +}; + +// Event handlers + +DatePickerDialog.prototype.handleOkButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + switch (event.key) { + case 'Tab': + if (!event.shiftKey) { + this.prevYearNode.focus(); + flag = true; + } + break; + + case 'Esc': + case 'Escape': + this.close(); + flag = true; + break; + + default: + break; + } + break; + + case 'click': + this.setTextboxDate(); + this.close(); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handleCancelButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + switch (event.key) { + case 'Esc': + case 'Escape': + this.close(); + flag = true; + break; + + default: + break; + } + break; + + case 'click': + this.close(); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handleNextYearButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + switch (event.key) { + case 'Esc': + case 'Escape': + this.close(); + flag = true; + break; + + case 'Enter': + this.moveToNextYear(); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + this.moveToNextYear(); + this.setFocusDay(false); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handlePreviousYearButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + switch (event.key) { + case 'Enter': + this.moveToPreviousYear(); + this.setFocusDay(false); + flag = true; + break; + + case 'Tab': + if (event.shiftKey) { + this.okButtonNode.focus(); + flag = true; + } + break; + + case 'Esc': + case 'Escape': + this.close(); + flag = true; + break; + + default: + break; + } + + break; + + case 'click': + this.moveToPreviousYear(); + this.setFocusDay(false); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handleNextMonthButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + switch (event.key) { + case 'Esc': + case 'Escape': + this.close(); + flag = true; + break; + + case 'Enter': + this.moveToNextMonth(); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + this.moveToNextMonth(); + this.setFocusDay(false); + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handlePreviousMonthButton = function (event) { + var flag = false; + + switch (event.type) { + case 'keydown': + switch (event.key) { + case 'Esc': + case 'Escape': + this.close(); + flag = true; + break; + + case 'Enter': + this.moveToPreviousMonth(); + this.setFocusDay(false); + flag = true; + break; + } + + break; + + case 'click': + this.moveToPreviousMonth(); + this.setFocusDay(false); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handleDayKeyDown = function (event) { + var flag = false; + + switch (event.key) { + case 'Esc': + case 'Escape': + this.close(); + break; + + case ' ': + this.setTextboxDate(event.currentTarget); + flag = true; + break; + + case 'Enter': + this.setTextboxDate(event.currentTarget); + this.close(); + flag = true; + break; + + case 'Tab': + this.cancelButtonNode.focus(); + if (event.shiftKey) { + this.nextYearNode.focus(); + } + this.setMessage(''); + flag = true; + break; + + case 'Right': + case 'ArrowRight': + this.moveFocusToNextDay(); + flag = true; + break; + + case 'Left': + case 'ArrowLeft': + this.moveFocusToPreviousDay(); + flag = true; + break; + + case 'Down': + case 'ArrowDown': + this.moveFocusToNextWeek(); + flag = true; + break; + + case 'Up': + case 'ArrowUp': + this.moveFocusToPreviousWeek(); + flag = true; + break; + + case 'PageUp': + if (event.shiftKey) { + this.moveToPreviousYear(); + } else { + this.moveToPreviousMonth(); + } + this.setFocusDay(); + flag = true; + break; + + case 'PageDown': + if (event.shiftKey) { + this.moveToNextYear(); + } else { + this.moveToNextMonth(); + } + this.setFocusDay(); + flag = true; + break; + + case 'Home': + this.moveFocusToFirstDayOfWeek(); + flag = true; + break; + + case 'End': + this.moveFocusToLastDayOfWeek(); + flag = true; + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handleDayClick = function (event) { + if (!this.isDayDisabled(event.currentTarget)) { + this.setTextboxDate(event.currentTarget); + this.close(); + } + + event.stopPropagation(); + event.preventDefault(); +}; + +DatePickerDialog.prototype.handleDayFocus = function () { + this.setMessage(this.messageCursorKeys); +}; + +DatePickerDialog.prototype.handleButtonKeydown = function (event) { + if (event.key === 'Enter' || event.key === ' ') { + this.open(); + this.setFocusDay(); + + event.stopPropagation(); + event.preventDefault(); + } +}; + +DatePickerDialog.prototype.handleButtonClick = function (event) { + if (this.isOpen()) { + this.close(); + } else { + this.open(); + this.setFocusDay(); + } + + event.stopPropagation(); + event.preventDefault(); +}; + +DatePickerDialog.prototype.handleBackgroundMouseUp = function (event) { + if ( + !this.buttonNode.contains(event.target) && + !this.dialogNode.contains(event.target) + ) { + if (this.isOpen()) { + this.close(false); + event.stopPropagation(); + event.preventDefault(); + } + } +}; + +// Initialize menu button date picker + +window.addEventListener('load', function () { + var datePickers = document.querySelectorAll('.datepicker'); + + datePickers.forEach(function (dp) { + new DatePickerDialog(dp); + }); +}); diff --git a/examples/dialog-modal/js/dialog.js b/examples/dialog-modal/js/dialog.js index 1dcd42a47b..0d2242e5eb 100644 --- a/examples/dialog-modal/js/dialog.js +++ b/examples/dialog-modal/js/dialog.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -29,8 +29,10 @@ aria.Utils = aria.Utils || {}; aria.Utils.focusFirstDescendant = function (element) { for (var i = 0; i < element.childNodes.length; i++) { var child = element.childNodes[i]; - if (aria.Utils.attemptFocus(child) || - aria.Utils.focusFirstDescendant(child)) { + if ( + aria.Utils.attemptFocus(child) || + aria.Utils.focusFirstDescendant(child) + ) { return true; } } @@ -47,8 +49,10 @@ aria.Utils = aria.Utils || {}; aria.Utils.focusLastDescendant = function (element) { for (var i = element.childNodes.length - 1; i >= 0; i--) { var child = element.childNodes[i]; - if (aria.Utils.attemptFocus(child) || - aria.Utils.focusLastDescendant(child)) { + if ( + aria.Utils.attemptFocus(child) || + aria.Utils.focusLastDescendant(child) + ) { return true; } } @@ -70,12 +74,11 @@ aria.Utils = aria.Utils || {}; aria.Utils.IgnoreUtilFocusChanges = true; try { element.focus(); - } - catch (e) { + } catch (e) { // continue regardless of error } aria.Utils.IgnoreUtilFocusChanges = false; - return (document.activeElement === element); + return document.activeElement === element; }; // end attemptFocus /* Modals can open modals. Keep track of them with this array. */ @@ -144,7 +147,8 @@ aria.Utils = aria.Utils || {}; }); if (!isDialog) { throw new Error( - 'Dialog() requires a DOM element with ARIA role of dialog or alertdialog.'); + 'Dialog() requires a DOM element with ARIA role of dialog or alertdialog.' + ); } // Wrap in an individual backdrop element if one doesn't exist @@ -153,11 +157,13 @@ aria.Utils = aria.Utils || {}; var backdropClass = 'dialog-backdrop'; if (this.dialogNode.parentNode.classList.contains(backdropClass)) { this.backdropNode = this.dialogNode.parentNode; - } - else { + } else { this.backdropNode = document.createElement('div'); this.backdropNode.className = backdropClass; - this.dialogNode.parentNode.insertBefore(this.backdropNode, this.dialogNode); + this.dialogNode.parentNode.insertBefore( + this.backdropNode, + this.dialogNode + ); this.backdropNode.appendChild(this.dialogNode); } this.backdropNode.classList.add('active'); @@ -167,22 +173,19 @@ aria.Utils = aria.Utils || {}; if (typeof focusAfterClosed === 'string') { this.focusAfterClosed = document.getElementById(focusAfterClosed); - } - else if (typeof focusAfterClosed === 'object') { + } else if (typeof focusAfterClosed === 'object') { this.focusAfterClosed = focusAfterClosed; - } - else { + } else { throw new Error( - 'the focusAfterClosed parameter is required for the aria.Dialog constructor.'); + 'the focusAfterClosed parameter is required for the aria.Dialog constructor.' + ); } if (typeof focusFirst === 'string') { this.focusFirst = document.getElementById(focusFirst); - } - else if (typeof focusFirst === 'object') { + } else if (typeof focusFirst === 'object') { this.focusFirst = focusFirst; - } - else { + } else { this.focusFirst = null; } @@ -190,12 +193,16 @@ aria.Utils = aria.Utils || {}; // While this dialog is open, we use these to make sure that focus never // leaves the document even if dialogNode is the first or last node. var preDiv = document.createElement('div'); - this.preNode = this.dialogNode.parentNode.insertBefore(preDiv, - this.dialogNode); + this.preNode = this.dialogNode.parentNode.insertBefore( + preDiv, + this.dialogNode + ); this.preNode.tabIndex = 0; var postDiv = document.createElement('div'); - this.postNode = this.dialogNode.parentNode.insertBefore(postDiv, - this.dialogNode.nextSibling); + this.postNode = this.dialogNode.parentNode.insertBefore( + postDiv, + this.dialogNode.nextSibling + ); this.postNode.tabIndex = 0; // If this modal is opening on top of one that is already open, @@ -211,8 +218,7 @@ aria.Utils = aria.Utils || {}; if (this.focusFirst) { this.focusFirst.focus(); - } - else { + } else { aria.Utils.focusFirstDescendant(this.dialogNode); } @@ -247,8 +253,7 @@ aria.Utils = aria.Utils || {}; // If a dialog was open underneath this one, restore its listeners. if (aria.OpenDialogList.length > 0) { aria.getCurrentDialog().addListeners(); - } - else { + } else { document.body.classList.remove(aria.Utils.dialogOpenClass); } }; // end close @@ -266,8 +271,11 @@ aria.Utils = aria.Utils || {}; * Optional ID or DOM node specifying where to place focus in the new dialog when it opens. * If not specified, the first focusable element will receive focus. */ - aria.Dialog.prototype.replace = function (newDialogId, newFocusAfterClosed, - newFocusFirst) { + aria.Dialog.prototype.replace = function ( + newDialogId, + newFocusAfterClosed, + newFocusFirst + ) { var closedDialog = aria.getCurrentDialog(); aria.OpenDialogList.pop(); this.removeListeners(); @@ -295,8 +303,7 @@ aria.Utils = aria.Utils || {}; var currentDialog = aria.getCurrentDialog(); if (currentDialog.dialogNode.contains(event.target)) { currentDialog.lastFocus = event.target; - } - else { + } else { aria.Utils.focusFirstDescendant(currentDialog.dialogNode); if (currentDialog.lastFocus == document.activeElement) { aria.Utils.focusLastDescendant(currentDialog.dialogNode); @@ -316,12 +323,14 @@ aria.Utils = aria.Utils || {}; } }; // end closeDialog - window.replaceDialog = function (newDialogId, newFocusAfterClosed, - newFocusFirst) { + window.replaceDialog = function ( + newDialogId, + newFocusAfterClosed, + newFocusFirst + ) { var topDialog = aria.getCurrentDialog(); if (topDialog.dialogNode.contains(document.activeElement)) { topDialog.replace(newDialogId, newFocusAfterClosed, newFocusFirst); } }; // end replaceDialog - -}()); +})(); diff --git a/examples/disclosure/js/disclosureButton.js b/examples/disclosure/js/disclosureButton.js index ab10bc986d..7cf26207f6 100644 --- a/examples/disclosure/js/disclosureButton.js +++ b/examples/disclosure/js/disclosureButton.js @@ -1,31 +1,29 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: ButtonExpand.js -* -* Desc: Checkbox widget that implements ARIA Authoring Practices -* for a menu of links -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: ButtonExpand.js + * + * Desc: Checkbox widget that implements ARIA Authoring Practices + * for a menu of links + */ 'use strict'; /* -* @constructor ButtonExpand -* -* -*/ + * @constructor ButtonExpand + * + * + */ var ButtonExpand = function (domNode) { - this.domNode = domNode; this.keyCode = Object.freeze({ - 'RETURN': 13 + RETURN: 13, }); }; ButtonExpand.prototype.init = function () { - this.controlledNode = false; var id = this.domNode.getAttribute('aria-controls'); @@ -37,52 +35,41 @@ ButtonExpand.prototype.init = function () { this.domNode.setAttribute('aria-expanded', 'false'); this.hideContent(); - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); }; ButtonExpand.prototype.showContent = function () { - if (this.controlledNode) { this.controlledNode.style.display = 'block'; } - }; ButtonExpand.prototype.hideContent = function () { - if (this.controlledNode) { this.controlledNode.style.display = 'none'; } - }; ButtonExpand.prototype.toggleExpand = function () { - if (this.domNode.getAttribute('aria-expanded') === 'true') { this.domNode.setAttribute('aria-expanded', 'false'); this.hideContent(); - } - else { + } else { this.domNode.setAttribute('aria-expanded', 'true'); this.showContent(); } - }; /* EVENT HANDLERS */ ButtonExpand.prototype.handleKeydown = function (event) { - console.log('[keydown]'); switch (event.keyCode) { - case this.keyCode.RETURN: - this.toggleExpand(); event.stopPropagation(); @@ -92,7 +79,6 @@ ButtonExpand.prototype.handleKeydown = function (event) { default: break; } - }; ButtonExpand.prototype.handleClick = function (event) { @@ -109,13 +95,17 @@ ButtonExpand.prototype.handleBlur = function (event) { /* Initialize Hide/Show Buttons */ -window.addEventListener('load', function (event) { - - var buttons = document.querySelectorAll('button[aria-expanded][aria-controls]'); - - for (var i = 0; i < buttons.length; i++) { - var be = new ButtonExpand(buttons[i]); - be.init(); - } - -}, false); +window.addEventListener( + 'load', + function (event) { + var buttons = document.querySelectorAll( + 'button[aria-expanded][aria-controls]' + ); + + for (var i = 0; i < buttons.length; i++) { + var be = new ButtonExpand(buttons[i]); + be.init(); + } + }, + false +); diff --git a/examples/disclosure/js/disclosureMenu.js b/examples/disclosure/js/disclosureMenu.js index 4eb39a84df..b70914a7d6 100644 --- a/examples/disclosure/js/disclosureMenu.js +++ b/examples/disclosure/js/disclosureMenu.js @@ -1,9 +1,9 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* Supplemental JS for the disclosure menu keyboard behavior -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * Supplemental JS for the disclosure menu keyboard behavior + */ 'use strict'; @@ -16,7 +16,9 @@ var DisclosureNav = function (domNode) { }; DisclosureNav.prototype.init = function () { - var buttons = this.rootNode.querySelectorAll('button[aria-expanded][aria-controls]'); + var buttons = this.rootNode.querySelectorAll( + 'button[aria-expanded][aria-controls]' + ); for (var i = 0; i < buttons.length; i++) { var button = buttons[i]; var menu = button.parentNode.querySelector('ul'); @@ -59,7 +61,11 @@ DisclosureNav.prototype.toggleExpand = function (index, expanded) { } }; -DisclosureNav.prototype.controlFocusByKey = function (keyboardEvent, nodeList, currentIndex) { +DisclosureNav.prototype.controlFocusByKey = function ( + keyboardEvent, + nodeList, + currentIndex +) { switch (keyboardEvent.key) { case 'ArrowUp': case 'ArrowLeft': @@ -105,7 +111,11 @@ DisclosureNav.prototype.handleButtonKeyDown = function (event) { } // move focus into the open menu if the current menu is open - else if (this.useArrowKeys && this.openIndex === targetButtonIndex && event.key === 'ArrowDown') { + else if ( + this.useArrowKeys && + this.openIndex === targetButtonIndex && + event.key === 'ArrowDown' + ) { event.preventDefault(); this.controlledNodes[this.openIndex].querySelector('a').focus(); } @@ -128,7 +138,9 @@ DisclosureNav.prototype.handleMenuKeyDown = function (event) { return; } - var menuLinks = Array.prototype.slice.call(this.controlledNodes[this.openIndex].querySelectorAll('a')); + var menuLinks = Array.prototype.slice.call( + this.controlledNodes[this.openIndex].querySelectorAll('a') + ); var currentIndex = menuLinks.indexOf(document.activeElement); // close on escape @@ -150,37 +162,41 @@ DisclosureNav.prototype.updateKeyControls = function (useArrowKeys) { /* Initialize Disclosure Menus */ -window.addEventListener('load', function (event) { - var menus = document.querySelectorAll('.disclosure-nav'); - var disclosureMenus = []; +window.addEventListener( + 'load', + function (event) { + var menus = document.querySelectorAll('.disclosure-nav'); + var disclosureMenus = []; - for (var i = 0; i < menus.length; i++) { - disclosureMenus[i] = new DisclosureNav(menus[i]); - disclosureMenus[i].init(); - } - - // listen to arrow key checkbox - var arrowKeySwitch = document.getElementById('arrow-behavior-switch'); - arrowKeySwitch.addEventListener('change', function (event) { - var checked = arrowKeySwitch.checked; - for (var i = 0; i < disclosureMenus.length; i++) { - disclosureMenus[i].updateKeyControls(checked); + for (var i = 0; i < menus.length; i++) { + disclosureMenus[i] = new DisclosureNav(menus[i]); + disclosureMenus[i].init(); } - }); - - // fake link behavior - var links = document.querySelectorAll('[href="#mythical-page-content"]'); - var examplePageHeading = document.getElementById('mythical-page-heading'); - for (var k = 0; k < links.length; k++) { - links[k].addEventListener('click', function (event) { - var pageTitle = event.target.innerText; - examplePageHeading.innerText = pageTitle; - - // handle aria-current - for (var n = 0; n < links.length; n++) { - links[n].removeAttribute('aria-current'); + + // listen to arrow key checkbox + var arrowKeySwitch = document.getElementById('arrow-behavior-switch'); + arrowKeySwitch.addEventListener('change', function (event) { + var checked = arrowKeySwitch.checked; + for (var i = 0; i < disclosureMenus.length; i++) { + disclosureMenus[i].updateKeyControls(checked); } - this.setAttribute('aria-current', 'page'); }); - } -}, false); + + // fake link behavior + var links = document.querySelectorAll('[href="#mythical-page-content"]'); + var examplePageHeading = document.getElementById('mythical-page-heading'); + for (var k = 0; k < links.length; k++) { + links[k].addEventListener('click', function (event) { + var pageTitle = event.target.innerText; + examplePageHeading.innerText = pageTitle; + + // handle aria-current + for (var n = 0; n < links.length; n++) { + links[n].removeAttribute('aria-current'); + } + this.setAttribute('aria-current', 'page'); + }); + } + }, + false +); diff --git a/examples/feed/js/feed.js b/examples/feed/js/feed.js index f4390fcb5d..6f84be649e 100644 --- a/examples/feed/js/feed.js +++ b/examples/feed/js/feed.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -40,10 +40,9 @@ aria.Feed.prototype.focusItem = function (item) { aria.Feed.prototype.mapKeyShortcut = function (event) { var key = event.which || event.keyCode; - var focusedArticle = - aria.Utils.matches(event.target, '[role="article"]') ? - event.target : - aria.Utils.getAncestorBySelector(event.target, '[role="article"]'); + var focusedArticle = aria.Utils.matches(event.target, '[role="article"]') + ? event.target + : aria.Utils.getAncestorBySelector(event.target, '[role="article"]'); if (!focusedArticle) { return; @@ -82,5 +81,4 @@ aria.Feed.prototype.mapKeyShortcut = function (event) { } break; } - }; diff --git a/examples/feed/js/feedDisplay.js b/examples/feed/js/feedDisplay.js index 654d777ff3..870563fea9 100644 --- a/examples/feed/js/feedDisplay.js +++ b/examples/feed/js/feedDisplay.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -12,13 +12,13 @@ var aria = aria || {}; aria.RestaurantData = [ { - name: 'Tito\'s Tacos', + name: "Tito's Tacos", rating: 5, type: 'Mexican, Tacos', street: '123 Blueberry Ln', citystate: 'San Dimas, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { name: 'Sakura Sushi', @@ -27,7 +27,7 @@ aria.RestaurantData = [ street: '123 Blueberry Ln', citystate: 'Pomona, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { name: 'Prime Steakhouse', @@ -36,7 +36,7 @@ aria.RestaurantData = [ street: '123 Blueberry Ln', citystate: 'Claremont, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { name: 'The Pizza Factory', @@ -45,16 +45,16 @@ aria.RestaurantData = [ street: '123 Blueberry Ln', citystate: 'Pomona, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { - name: 'Emperor\'s Mongolian', + name: "Emperor's Mongolian", rating: 5, type: 'Mongolian, Barbequeue, Buffets', street: '123 Blueberry Ln', citystate: 'La Verne, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { name: 'Backyard Grill', @@ -63,7 +63,7 @@ aria.RestaurantData = [ street: '123 Blueberry Ln', citystate: 'San Dimas, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { name: 'Taste Kitchen', @@ -72,7 +72,7 @@ aria.RestaurantData = [ street: '123 Blueberry Ln', citystate: 'Claremont, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { name: 'Bon Appetit', @@ -81,16 +81,16 @@ aria.RestaurantData = [ street: '123 Blueberry Ln', citystate: 'La Verne, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { - name: 'Sally\'s Sandwiches', + name: "Sally's Sandwiches", rating: 3, type: 'Sandwiches, American', street: '123 Blueberry Ln', citystate: 'San Dimas, CA', phone: '(111) 111-1111', - image: '' + image: '', }, { name: 'The HotPot Spot', @@ -99,8 +99,8 @@ aria.RestaurantData = [ street: '123 Blueberry Ln', citystate: 'Pomona, CA', phone: '(111) 111-1111', - image: '' - } + image: '', + }, ]; aria.FeedDisplay = function (feed, fetchData) { @@ -135,10 +135,14 @@ aria.FeedDisplay.prototype.loadData = function () { var loadingItems = []; - Array.prototype.forEach.call(feedData, function (itemData) { - var newFeedItem = this.renderItemData(itemData); - loadingItems.push(newFeedItem); - }, this); + Array.prototype.forEach.call( + feedData, + function (itemData) { + var newFeedItem = this.renderItemData(itemData); + loadingItems.push(newFeedItem); + }, + this + ); this.delayRender( loadingItems, @@ -165,9 +169,12 @@ aria.FeedDisplay.prototype.delayRender = function (items, onRenderDone) { feedItem.setAttribute('aria-setsize', this.feedItems.length); }, this); - setTimeout((function () { - this.delayRender(items, onRenderDone); - }).bind(this), this.loadingDelay); + setTimeout( + function () { + this.delayRender(items, onRenderDone); + }.bind(this), + this.loadingDelay + ); }; aria.FeedDisplay.prototype.renderItemData = function (itemData) { @@ -188,31 +195,42 @@ aria.FeedDisplay.prototype.renderItemData = function (itemData) { feedItem.setAttribute('aria-labelledby', restaurantID); if (itemData.image) { - itemContent += '
      ' + - itemData.image + - '
      '; + itemContent += '
      ' + itemData.image + '
      '; } - itemContent += '
      ' + - itemData.name + - '
      '; + itemContent += + '
      ' + + itemData.name + + '
      '; if (itemData.rating) { var ratingID = 'restaurant-rating-' + this.feedSize; - itemContent += '
      ' + - '' + - '
      '; + itemContent += + '
      ' + + '' + + '
      '; describedbyIDs.push(ratingID); } if (itemData.type) { var typeID = 'restaurant-type-' + this.feedSize; - itemContent += '
      ' + - itemData.type + - '
      '; + itemContent += + '
      ' + + itemData.type + + '
      '; describedbyIDs.push(typeID); } @@ -227,21 +245,18 @@ aria.FeedDisplay.prototype.renderItemData = function (itemData) { describedbyIDs.push(locationID); if (itemData.street) { - locationContent += '
      ' + - itemData.street + - '
      '; + locationContent += + '
      ' + itemData.street + '
      '; } if (itemData.citystate) { - locationContent += '
      ' + - itemData.citystate + - '
      '; + locationContent += + '
      ' + itemData.citystate + '
      '; } if (itemData.phone) { - locationContent += '
      ' + - itemData.phone + - '
      '; + locationContent += + '
      ' + itemData.phone + '
      '; } locationBlock.innerHTML = locationContent; @@ -251,7 +266,8 @@ aria.FeedDisplay.prototype.renderItemData = function (itemData) { var actions = document.createElement('div'); actions.className = 'restaurant-actions'; - actions.innerHTML = ''; + actions.innerHTML = + ''; feedItem.appendChild(actions); return feedItem; @@ -264,7 +280,7 @@ aria.FeedDisplay.prototype.setupEvents = function () { aria.FeedDisplay.prototype.handleScroll = function () { var now = Date.now(); - if ((this.lastChecked + 100 - now) < 0) { + if (this.lastChecked + 100 - now < 0) { this.checkLoadMore(); this.lastChecked = now; } @@ -276,13 +292,14 @@ aria.FeedDisplay.prototype.checkLoadMore = function () { } var lastFeedItem = this.feedItems[this.feedItems.length - 1]; - var scrollTop = window.pageYOffset || - document.documentElement.scrollTop || - document.body.scrollTop || - 0; + var scrollTop = + window.pageYOffset || + document.documentElement.scrollTop || + document.body.scrollTop || + 0; var scrollBottom = scrollTop + window.innerHeight; - if (scrollBottom >= (lastFeedItem.offsetTop - 300)) { + if (scrollBottom >= lastFeedItem.offsetTop - 300) { this.loadData(); } }; diff --git a/examples/feed/js/main.js b/examples/feed/js/main.js index 3cc5b7a412..3731fc2078 100644 --- a/examples/feed/js/main.js +++ b/examples/feed/js/main.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -13,17 +13,11 @@ window.addEventListener('load', function () { var feedNode = document.getElementById('restaurant-feed'); var delaySelect = document.getElementById('delay-time-select'); - var restaurantFeed = new aria.Feed( - feedNode, - delaySelect - ); + var restaurantFeed = new aria.Feed(feedNode, delaySelect); - var restaurantFeedDisplay = new aria.FeedDisplay( - restaurantFeed, - function () { - return aria.RestaurantData; - } - ); + var restaurantFeedDisplay = new aria.FeedDisplay(restaurantFeed, function () { + return aria.RestaurantData; + }); restaurantFeedDisplay.setDelay(delaySelect.value); delaySelect.addEventListener('change', function () { diff --git a/examples/grid/js/dataGrid.js b/examples/grid/js/dataGrid.js index 3887f2b0ce..c3e71d44af 100644 --- a/examples/grid/js/dataGrid.js +++ b/examples/grid/js/dataGrid.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -17,7 +17,7 @@ var aria = aria || {}; aria.SortType = { ASCENDING: 'ascending', DESCENDING: 'descending', - NONE: 'none' + NONE: 'none', }; /** @@ -29,7 +29,7 @@ aria.GridSelector = { CELL: 'th, td, [role="gridcell"]', SCROLL_ROW: 'tr:not([data-fixed]), [role="row"]', SORT_HEADER: 'th[aria-sort]', - TABBABLE: '[tabindex="0"]' + TABBABLE: '[tabindex="0"]', }; /** @@ -37,7 +37,7 @@ aria.GridSelector = { * CSS Class names */ aria.CSSClass = { - HIDDEN: 'hidden' + HIDDEN: 'hidden', }; /** @@ -64,17 +64,23 @@ aria.Grid = function (gridNode) { this.keysIndicator = document.getElementById('arrow-keys-indicator'); - aria.Utils.bindMethods(this, - 'checkFocusChange', 'checkPageChange', 'checkRestructureGrid', - 'delegateButtonHandler', 'focusClickedCell', 'restructureGrid', - 'showKeysIndicator', 'hideKeysIndicator'); + aria.Utils.bindMethods( + this, + 'checkFocusChange', + 'checkPageChange', + 'checkRestructureGrid', + 'delegateButtonHandler', + 'focusClickedCell', + 'restructureGrid', + 'showKeysIndicator', + 'hideKeysIndicator' + ); this.setupFocusGrid(); this.setFocusPointer(0, 0); if (this.paginationEnabled) { this.setupPagination(); - } - else { + } else { this.perPage = this.grid.length; } @@ -90,31 +96,30 @@ aria.Grid.prototype.setupFocusGrid = function () { Array.prototype.forEach.call( this.gridNode.querySelectorAll(aria.GridSelector.ROW), - (function (row) { + function (row) { var rowCells = []; Array.prototype.forEach.call( row.querySelectorAll(aria.GridSelector.CELL), - (function (cell) { + function (cell) { var focusableSelector = '[tabindex]'; if (aria.Utils.matches(cell, focusableSelector)) { rowCells.push(cell); - } - else { + } else { var focusableCell = cell.querySelector(focusableSelector); if (focusableCell) { rowCells.push(focusableCell); } } - }).bind(this) + }.bind(this) ); if (rowCells.length) { this.grid.push(rowCells); } - }).bind(this) + }.bind(this) ); if (this.paginationEnabled) { @@ -148,10 +153,8 @@ aria.Grid.prototype.setFocusPointer = function (row, col) { this.grid[this.focusedRow][this.focusedCol].setAttribute('tabindex', -1); } - this.grid[row][col] - .removeEventListener('focus', this.showKeysIndicator); - this.grid[row][col] - .removeEventListener('blur', this.hideKeysIndicator); + this.grid[row][col].removeEventListener('focus', this.showKeysIndicator); + this.grid[row][col].removeEventListener('blur', this.hideKeysIndicator); // Disable navigation if focused on an input this.navigationDisabled = aria.Utils.matches(this.grid[row][col], 'input'); @@ -160,10 +163,8 @@ aria.Grid.prototype.setFocusPointer = function (row, col) { this.focusedRow = row; this.focusedCol = col; - this.grid[row][col] - .addEventListener('focus', this.showKeysIndicator); - this.grid[row][col] - .addEventListener('blur', this.hideKeysIndicator); + this.grid[row][col].addEventListener('focus', this.showKeysIndicator); + this.grid[row][col].addEventListener('blur', this.hideKeysIndicator); return true; }; @@ -202,8 +203,9 @@ aria.Grid.prototype.isValidCell = function (row, col) { * Returns whether or not the cell has been hidden. */ aria.Grid.prototype.isHidden = function (row, col) { - var cell = this.gridNode.querySelectorAll(aria.GridSelector.ROW)[row] - .querySelectorAll(aria.GridSelector.CELL)[col]; + var cell = this.gridNode + .querySelectorAll(aria.GridSelector.ROW) + [row].querySelectorAll(aria.GridSelector.CELL)[col]; return aria.Utils.hasClass(cell, aria.CSSClass.HIDDEN); }; @@ -225,10 +227,14 @@ aria.Grid.prototype.clearEvents = function () { window.removeEventListener('resize', this.checkRestructureGrid); } - this.grid[this.focusedRow][this.focusedCol] - .removeEventListener('focus', this.showKeysIndicator); - this.grid[this.focusedRow][this.focusedCol] - .removeEventListener('blur', this.hideKeysIndicator); + this.grid[this.focusedRow][this.focusedCol].removeEventListener( + 'focus', + this.showKeysIndicator + ); + this.grid[this.focusedRow][this.focusedCol].removeEventListener( + 'blur', + this.hideKeysIndicator + ); }; /** @@ -275,8 +281,10 @@ aria.Grid.prototype.showKeysIndicator = function () { }; aria.Grid.prototype.hideKeysIndicator = function () { - if (this.keysIndicator && - this.grid[this.focusedRow][this.focusedCol].tabIndex === 0) { + if ( + this.keysIndicator && + this.grid[this.focusedRow][this.focusedCol].tabIndex === 0 + ) { aria.Utils.addClass(this.keysIndicator, 'hidden'); } }; @@ -362,15 +370,16 @@ aria.Grid.prototype.checkFocusChange = function (event) { aria.Grid.prototype.findFocusedItem = function (focusedTarget) { var focusedCell = this.grid[this.focusedRow][this.focusedCol]; - if (focusedCell === focusedTarget || - focusedCell.contains(focusedTarget)) { + if (focusedCell === focusedTarget || focusedCell.contains(focusedTarget)) { return; } for (var i = 0; i < this.grid.length; i++) { for (var j = 0; j < this.grid[i].length; j++) { - if (this.grid[i][j] === focusedTarget || - this.grid[i][j].contains(focusedTarget)) { + if ( + this.grid[i][j] === focusedTarget || + this.grid[i][j].contains(focusedTarget) + ) { this.setFocusPointer(i, j); return; } @@ -415,7 +424,7 @@ aria.Grid.prototype.focusClickedCell = function (event) { aria.Grid.prototype.delegateButtonHandler = function (event) { var key = event.which || event.keyCode; var target = event.target; - var isClickEvent = (event.type === 'click'); + var isClickEvent = event.type === 'click'; if (!target) { return; @@ -424,11 +433,7 @@ aria.Grid.prototype.delegateButtonHandler = function (event) { if ( target.parentNode && target.parentNode.matches('th[aria-sort]') && - ( - isClickEvent || - key === aria.KeyCode.SPACE || - key === aria.KeyCode.RETURN - ) + (isClickEvent || key === aria.KeyCode.SPACE || key === aria.KeyCode.RETURN) ) { event.preventDefault(); this.handleSort(target.parentNode); @@ -436,25 +441,15 @@ aria.Grid.prototype.delegateButtonHandler = function (event) { if ( aria.Utils.matches(target, '.editable-text, .edit-text-button') && - ( - isClickEvent || - key === aria.KeyCode.RETURN - ) + (isClickEvent || key === aria.KeyCode.RETURN) ) { event.preventDefault(); - this.toggleEditMode( - this.findClosest(target, '.editable-text'), - true, - true - ); + this.toggleEditMode(this.findClosest(target, '.editable-text'), true, true); } if ( aria.Utils.matches(target, '.edit-text-input') && - ( - key === aria.KeyCode.RETURN || - key === aria.KeyCode.ESC - ) + (key === aria.KeyCode.RETURN || key === aria.KeyCode.ESC) ) { event.preventDefault(); this.toggleEditMode( @@ -487,8 +482,7 @@ aria.Grid.prototype.toggleEditMode = function (editCell, toggleOn, updateText) { if (toggleOn) { onNode.value = offNode.innerText; - } - else if (updateText) { + } else if (updateText) { onNode.innerText = offNode.value; } @@ -519,8 +513,7 @@ aria.Grid.prototype.handleSort = function (headerNode) { if (sortType === aria.SortType.ASCENDING) { sortType = aria.SortType.DESCENDING; - } - else { + } else { sortType = aria.SortType.ASCENDING; } @@ -532,8 +525,7 @@ aria.Grid.prototype.handleSort = function (headerNode) { if (sortType === aria.SortType.ASCENDING) { return row1Value - row2Value; - } - else { + } else { return row2Value - row1Value; } }; @@ -565,9 +557,11 @@ aria.Grid.prototype.sortRows = function (compareFn) { dataRows.sort(compareFn); - dataRows.forEach((function (row) { - rowWrapper.appendChild(row); - }).bind(this)); + dataRows.forEach( + function (row) { + rowWrapper.appendChild(row); + }.bind(this) + ); }; /** @@ -584,7 +578,6 @@ aria.Grid.prototype.setupIndices = function () { for (var col = 0; col < cols.length; col++) { cols[col].setAttribute('aria-colindex', col + 1); } - } }; @@ -620,8 +613,7 @@ aria.Grid.prototype.checkPageChange = function (event) { if (key === aria.KeyCode.PAGE_UP) { event.preventDefault(); this.movePageUp(); - } - else if (key === aria.KeyCode.PAGE_DOWN) { + } else if (key === aria.KeyCode.PAGE_DOWN) { event.preventDefault(); this.movePageDown(); } @@ -650,8 +642,7 @@ aria.Grid.prototype.movePageDown = function () { * Whether to scroll the new page above or below the row index */ aria.Grid.prototype.showFromRow = function (startIndex, scrollDown) { - var dataRows = - this.gridNode.querySelectorAll(aria.GridSelector.SCROLL_ROW); + var dataRows = this.gridNode.querySelectorAll(aria.GridSelector.SCROLL_ROW); var reachedTop = false; var firstIndex = -1; var endIndex = -1; @@ -661,17 +652,9 @@ aria.Grid.prototype.showFromRow = function (startIndex, scrollDown) { } for (var i = 0; i < dataRows.length; i++) { - if ( - ( - scrollDown && - i >= startIndex && - i < startIndex + this.perPage) || - ( - !scrollDown && - i <= startIndex && - i > startIndex - this.perPage - ) + (scrollDown && i >= startIndex && i < startIndex + this.perPage) || + (!scrollDown && i <= startIndex && i > startIndex - this.perPage) ) { aria.Utils.removeClass(dataRows[i], aria.CSSClass.HIDDEN); @@ -684,8 +667,7 @@ aria.Grid.prototype.showFromRow = function (startIndex, scrollDown) { firstIndex = i; } endIndex = i; - } - else { + } else { aria.Utils.addClass(dataRows[i], aria.CSSClass.HIDDEN); } } @@ -718,8 +700,8 @@ aria.Grid.prototype.restructureGrid = function () { var currentWidth = 0; var focusedElement = this.gridNode.querySelector(aria.GridSelector.TABBABLE); - var shouldRefocus = (document.activeElement === focusedElement); - var focusedIndex = (this.focusedRow * this.grid[0].length + this.focusedCol); + var shouldRefocus = document.activeElement === focusedElement; + var focusedIndex = this.focusedRow * this.grid[0].length + this.focusedCol; var newRow = document.createElement('div'); newRow.setAttribute('role', 'row'); @@ -729,7 +711,7 @@ aria.Grid.prototype.restructureGrid = function () { cells.forEach(function (cell, index) { var cellWidth = cell.offsetWidth; - if (currentWidth > 0 && currentWidth >= (gridWidth - cellWidth)) { + if (currentWidth > 0 && currentWidth >= gridWidth - cellWidth) { newRow = document.createElement('div'); newRow.setAttribute('role', 'row'); this.gridNode.append(newRow); @@ -807,8 +789,7 @@ aria.Grid.prototype.getNextCell = function ( // jump to the next filled in cell. row--; } - } - else if (row >= rowCount || !this.grid[row][col]) { + } else if (row >= rowCount || !this.grid[row][col]) { row = 0; col++; } @@ -817,16 +798,14 @@ aria.Grid.prototype.getNextCell = function ( if (this.isValidCell(row, col)) { return { row: row, - col: col + col: col, }; - } - else if (this.isValidCell(currRow, currCol)) { + } else if (this.isValidCell(currRow, currCol)) { return { row: currRow, - col: currCol + col: currCol, }; - } - else { + } else { return false; } }; @@ -889,19 +868,15 @@ aria.Grid.prototype.toggleColumn = function (columnIndex, isShown) { var cellSelector = '[aria-colindex="' + columnIndex + '"]'; var columnCells = this.gridNode.querySelectorAll(cellSelector); - Array.prototype.forEach.call( - columnCells, - function (cell) { - if (isShown) { - aria.Utils.removeClass(cell, aria.CSSClass.HIDDEN); - } - else { - aria.Utils.addClass(cell, aria.CSSClass.HIDDEN); - } + Array.prototype.forEach.call(columnCells, function (cell) { + if (isShown) { + aria.Utils.removeClass(cell, aria.CSSClass.HIDDEN); + } else { + aria.Utils.addClass(cell, aria.CSSClass.HIDDEN); } - ); + }); - if (!isShown && this.focusedCol === (columnIndex - 1)) { + if (!isShown && this.focusedCol === columnIndex - 1) { // If focus was set on the hidden column, shift focus to the right var nextCell = this.getNextVisibleCell(1, 0); if (nextCell) { diff --git a/examples/grid/js/dataGrids.js b/examples/grid/js/dataGrids.js index 0ee01253df..d2351c4d70 100644 --- a/examples/grid/js/dataGrids.js +++ b/examples/grid/js/dataGrids.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -31,8 +31,7 @@ window.addEventListener('load', function () { if (toggledOn) { toggleButton.innerText = 'Hide Type and Category'; - } - else { + } else { toggleButton.innerText = 'Show Type and Category'; } }); diff --git a/examples/grid/js/layoutGrids.js b/examples/grid/js/layoutGrids.js index b0e9b65fcc..3136ecdc83 100644 --- a/examples/grid/js/layoutGrids.js +++ b/examples/grid/js/layoutGrids.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -41,14 +41,12 @@ window.addEventListener('load', function () { endIndexText.innerText = endIndex + 1; if (startIndex <= 0) { previousButton.setAttribute('disabled', 'true'); - } - else { + } else { previousButton.removeAttribute('disabled'); } if (endIndex >= 18) { nextButton.setAttribute('disabled', 'true'); - } - else { + } else { nextButton.removeAttribute('disabled'); } }); @@ -77,9 +75,9 @@ window.addEventListener('load', function () { firstGridCell.addEventListener('focus', setupInstructions); }); -function PillList (grid, input, submitButton, formUpdateText) { +function PillList(grid, input, submitButton, formUpdateText) { // Hardcoded to work for example 2 - this.pillIDs = {length: 2, 1: true, 2: true}; + this.pillIDs = { length: 2, 1: true, 2: true }; this.nextPillID = 3; this.grid = grid; this.input = input; @@ -89,7 +87,10 @@ function PillList (grid, input, submitButton, formUpdateText) { this.input.addEventListener('keydown', this.checkSubmitItem.bind(this)); this.submitButton.addEventListener('click', this.submitItemForm.bind(this)); this.grid.gridNode.addEventListener('click', this.checkRemovePill.bind(this)); - this.grid.gridNode.addEventListener('keydown', this.checkRemovePill.bind(this)); + this.grid.gridNode.addEventListener( + 'keydown', + this.checkRemovePill.bind(this) + ); } PillList.prototype.checkSubmitItem = function (event) { @@ -104,8 +105,7 @@ PillList.prototype.getRecipientsString = function () { var recipientCount = this.pillIDs.length; if (recipientCount === 1) { return '1 recipient total.'; - } - else { + } else { return recipientCount + ' recipients total.'; } }; @@ -115,7 +115,8 @@ PillList.prototype.submitItemForm = function () { this.addPillItem(newItem); this.input.value = ''; this.input.focus(); - this.formUpdateText.innerText = newItem + ' added. ' + this.getRecipientsString(); + this.formUpdateText.innerText = + newItem + ' added. ' + this.getRecipientsString(); }; PillList.prototype.addPillItem = function (recipientName) { @@ -132,15 +133,23 @@ PillList.prototype.addPillItem = function (recipientName) { newPillItem.innerHTML = '' + - '' + - recipientName + - '' + + '' + + recipientName + + '' + '' + '' + - '' + - 'X' + - '' + + '' + + 'X' + + '' + ''; this.grid.gridNode.append(newPillItem); @@ -159,12 +168,14 @@ PillList.prototype.addPillItem = function (recipientName) { PillList.prototype.checkRemovePill = function (event) { var pillItem, pillID, pillName; - var isClickEvent = (event.type === 'click'); + var isClickEvent = event.type === 'click'; var key = event.which || event.keyCode; - if (!isClickEvent && - key !== aria.KeyCode.RETURN && - key !== aria.KeyCode.SPACE) { + if ( + !isClickEvent && + key !== aria.KeyCode.RETURN && + key !== aria.KeyCode.SPACE + ) { return; } @@ -172,14 +183,14 @@ PillList.prototype.checkRemovePill = function (event) { pillItem = event.target.parentNode.parentNode; pillID = pillItem.getAttribute('data-id'); pillName = pillItem.querySelector('.pill-name').innerText; - } - else { + } else { return; } delete this.pillIDs[pillID]; this.pillIDs.length--; - this.formUpdateText.innerText = pillName + ' removed. ' + this.getRecipientsString(); + this.formUpdateText.innerText = + pillName + ' removed. ' + this.getRecipientsString(); pillItem.remove(); this.grid.setupFocusGrid(); @@ -187,8 +198,9 @@ PillList.prototype.checkRemovePill = function (event) { if (this.grid.isValidCell(this.grid.focusedRow, this.grid.focusedCol)) { // First, try to focus on the next pill this.grid.focusCell(this.grid.focusedRow, this.grid.focusedCol); - } - else if (this.grid.isValidCell(--this.grid.focusedRow, this.grid.focusedCol)) { + } else if ( + this.grid.isValidCell(--this.grid.focusedRow, this.grid.focusedCol) + ) { // If there is no next pill, try to focus on the previous pill this.grid.focusCell(this.grid.focusedRow, this.grid.focusedCol); } diff --git a/examples/grid/js/menuButton.js b/examples/grid/js/menuButton.js index 0006b26b29..c3d5124c20 100644 --- a/examples/grid/js/menuButton.js +++ b/examples/grid/js/menuButton.js @@ -1,8 +1,8 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + */ 'use strict'; @@ -13,13 +13,11 @@ */ window.addEventListener('load', function () { - var menuButtons = document.querySelectorAll('[aria-haspopup][aria-controls]'); [].forEach.call(menuButtons, function (menuButton) { if ( - menuButton && - menuButton.tagName.toLowerCase() === 'button' || + (menuButton && menuButton.tagName.toLowerCase() === 'button') || menuButton.getAttribute('role').toLowerCase() === 'button' ) { var mb = new aria.widget.MenuButton(menuButton); @@ -53,11 +51,11 @@ aria.Utils.findPos = function (element) { var yPosition = 0; while (element) { - xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft); - yPosition += (element.offsetTop - element.scrollTop + element.clientTop); + xPosition += element.offsetLeft - element.scrollLeft + element.clientLeft; + yPosition += element.offsetTop - element.scrollTop + element.clientTop; element = element.offsetParent; } - return {x: xPosition, y: yPosition}; + return { x: xPosition, y: yPosition }; }; /* ---------------------------------------------------------------- */ @@ -79,20 +77,19 @@ aria.widget = aria.widget || {}; */ aria.widget.Menu = function (node, menuButton) { - this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + RETURN: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); // Check fo DOM element node @@ -107,7 +104,6 @@ aria.widget.Menu = function (node, menuButton) { this.firstMenuItem = false; this.lastMenuItem = false; - }; /** @@ -119,7 +115,6 @@ aria.widget.Menu = function (node, menuButton) { */ aria.widget.Menu.prototype.initMenu = function () { - var self = this; var cn = this.menuNode.firstChild; @@ -152,7 +147,6 @@ aria.widget.Menu.prototype.initMenu = function () { self.eventFocus(event, self); }; cn.addEventListener('focus', eventFocus); - } } cn = cn.nextSibling; @@ -168,13 +162,12 @@ aria.widget.Menu.prototype.initMenu = function () { */ aria.widget.Menu.prototype.nextMenuItem = function (currentMenuItem) { - var mi = currentMenuItem.nextSibling; while (mi) { if ( - (mi.nodeType === Node.ELEMENT_NODE) && - (mi.getAttribute('role') === 'menuitem') + mi.nodeType === Node.ELEMENT_NODE && + mi.getAttribute('role') === 'menuitem' ) { mi.focus(); break; @@ -196,7 +189,6 @@ aria.widget.Menu.prototype.nextMenuItem = function (currentMenuItem) { */ aria.widget.Menu.prototype.previousMenuItem = function (currentMenuItem) { - var mi = currentMenuItem.previousSibling; while (mi) { @@ -226,12 +218,10 @@ aria.widget.Menu.prototype.previousMenuItem = function (currentMenuItem) { */ aria.widget.Menu.prototype.eventKeyDown = function (event, menu) { - var ct = event.currentTarget; var flag = false; switch (event.keyCode) { - case menu.keyCode.SPACE: case menu.keyCode.RETURN: menu.eventMouseClick(event, menu); @@ -269,7 +259,6 @@ aria.widget.Menu.prototype.eventKeyDown = function (event, menu) { event.stopPropagation(); event.preventDefault(); } - }; /** @@ -283,11 +272,9 @@ aria.widget.Menu.prototype.eventKeyDown = function (event, menu) { */ aria.widget.Menu.prototype.eventMouseClick = function (event, menu) { - var clickedItemText = event.target.innerText; this.menuButton.buttonNode.innerText = clickedItemText; menu.menuButton.closeMenu(true); - }; /** @@ -334,14 +321,13 @@ aria.widget.Menu.prototype.eventFocus = function (event, menu) { */ aria.widget.MenuButton = function (node) { - this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'UP': 38, - 'DOWN': 40 + TAB: 9, + RETURN: 13, + ESC: 27, + SPACE: 32, + UP: 38, + DOWN: 40, }); // Check fo DOM element node @@ -357,11 +343,10 @@ aria.widget.MenuButton = function (node) { if (node.tagName.toLowerCase() === 'a') { var url = node.getAttribute('href'); - if (url && url.length && (url.length > 0)) { + if (url && url.length && url.length > 0) { this.isLink = true; } } - }; /** @@ -424,7 +409,6 @@ aria.widget.MenuButton.prototype.openMenu = function () { */ aria.widget.MenuButton.prototype.closeMenu = function (force, focusMenuButton) { - if (typeof force !== 'boolean') { force = false; } @@ -434,12 +418,10 @@ aria.widget.MenuButton.prototype.closeMenu = function (force, focusMenuButton) { if ( force || - ( - !this.mouseInMenuButton && + (!this.mouseInMenuButton && this.menuNode && !this.menu.mouseInMenu && - !this.menu.menuHasFocus - ) + !this.menu.menuHasFocus) ) { this.menuNode.style.display = 'none'; if (focusMenuButton) { @@ -458,16 +440,13 @@ aria.widget.MenuButton.prototype.closeMenu = function (force, focusMenuButton) { */ aria.widget.MenuButton.prototype.toggleMenu = function () { - if (this.menuNode) { if (this.menuNode.style.display === 'block') { this.menuNode.style.display = 'none'; - } - else { + } else { this.menuNode.style.display = 'block'; } } - }; /** @@ -479,12 +458,10 @@ aria.widget.MenuButton.prototype.toggleMenu = function () { */ aria.widget.MenuButton.prototype.moveFocusToFirstMenuItem = function () { - if (this.menu.firstMenuItem) { this.openMenu(); this.menu.firstMenuItem.focus(); } - }; /** @@ -496,12 +473,10 @@ aria.widget.MenuButton.prototype.moveFocusToFirstMenuItem = function () { */ aria.widget.MenuButton.prototype.moveFocusToLastMenuItem = function () { - if (this.menu.lastMenuItem) { this.openMenu(); this.menu.lastMenuItem.focus(); } - }; /** @@ -515,11 +490,9 @@ aria.widget.MenuButton.prototype.moveFocusToLastMenuItem = function () { */ aria.widget.MenuButton.prototype.eventKeyDown = function (event, menuButton) { - var flag = false; switch (event.keyCode) { - case menuButton.keyCode.SPACE: menuButton.moveFocusToFirstMenuItem(); flag = true; @@ -556,7 +529,6 @@ aria.widget.MenuButton.prototype.eventKeyDown = function (event, menuButton) { event.stopPropagation(); event.preventDefault(); } - }; /** @@ -568,6 +540,9 @@ aria.widget.MenuButton.prototype.eventKeyDown = function (event, menuButton) { * NOTE: The menuButton parameter is needed to provide a reference to the specific * menuButton */ -aria.widget.MenuButton.prototype.eventMouseClick = function (event, menuButton) { +aria.widget.MenuButton.prototype.eventMouseClick = function ( + event, + menuButton +) { menuButton.moveFocusToFirstMenuItem(); }; diff --git a/examples/js/app.js b/examples/js/app.js index 3ef86e6cd3..db66a66ebd 100644 --- a/examples/js/app.js +++ b/examples/js/app.js @@ -21,7 +21,9 @@ } // The #browser_and_AT_support link - var supportLink = document.querySelector('a[href$="#browser_and_AT_support"]'); + var supportLink = document.querySelector( + 'a[href$="#browser_and_AT_support"]' + ); if (!supportLink) { return; } @@ -30,32 +32,34 @@ var urlPrefix = supportLink.getAttribute('href').split('#')[0]; // Expected outcome '../js/app.js' OR '../../js/app.js' - var scriptSource = document.querySelector('[src$="app.js"]').getAttribute('src'); + var scriptSource = document + .querySelector('[src$="app.js"]') + .getAttribute('src'); // Replace 'app.js' part with 'notice.html' var fetchSource = scriptSource.replace('app.js', './notice.html'); //fetch('https://raw.githack.com/w3c/aria-practices/1228-support-notice/examples/js/notice.html') fetch(fetchSource) - .then(function(response) { - // Return notice.html as text - return response.text(); - }) - .then(function(html) { - // Parse response as text/html - var parser = new DOMParser(); - return parser.parseFromString(html, "text/html"); - }) - .then(function(doc) { - // Get the details element from the parsed response - var noticeElement = doc.querySelector('details'); - // Rewrite links with the right urlPrefix - var links = doc.querySelectorAll('a[href^="/#"]'); - for (var i = 0; i < links.length; ++i) { - links[i].pathname = urlPrefix; - } - // Insert the support notice before the page's h1 - var heading = document.querySelector('h1'); - heading.parentNode.insertBefore(noticeElement, heading.nextSibling); - }) + .then(function (response) { + // Return notice.html as text + return response.text(); + }) + .then(function (html) { + // Parse response as text/html + var parser = new DOMParser(); + return parser.parseFromString(html, 'text/html'); + }) + .then(function (doc) { + // Get the details element from the parsed response + var noticeElement = doc.querySelector('details'); + // Rewrite links with the right urlPrefix + var links = doc.querySelectorAll('a[href^="/#"]'); + for (var i = 0; i < links.length; ++i) { + links[i].pathname = urlPrefix; + } + // Insert the support notice before the page's h1 + var heading = document.querySelector('h1'); + heading.parentNode.insertBefore(noticeElement, heading.nextSibling); + }); } -}()); +})(); diff --git a/examples/js/examples.js b/examples/js/examples.js index 10aa54ba6f..95e86eb4f3 100644 --- a/examples/js/examples.js +++ b/examples/js/examples.js @@ -22,7 +22,22 @@ var DEFAULT_INDENT = '  '; // Void elements according to “HTML 5.3: The HTML Syntax”: // https://w3c.github.io/html/syntax.html#void-elements. -var VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']; +var VOID_ELEMENTS = [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', +]; /** * Creates a slider widget using ARIA @@ -50,7 +65,12 @@ aria.widget.SourceCode = function () { * @method add * @memberof aria.widget.SourceCode */ -aria.widget.SourceCode.prototype.add = function (locationId, codeId, exampleHeaderId, cssJsFilesId) { +aria.widget.SourceCode.prototype.add = function ( + locationId, + codeId, + exampleHeaderId, + cssJsFilesId +) { this.location[this.location.length] = locationId; this.code[this.code.length] = codeId; this.exampleHeader[this.exampleHeader.length] = exampleHeaderId; @@ -78,7 +98,12 @@ aria.widget.SourceCode.prototype.make = function () { // Adds the "Open In CodePen" button by the example header if (this.exampleHeader[i]) { - addOpenInCodePenForm(i, this.exampleHeader[i], this.code[i], this.resources[i]); + addOpenInCodePenForm( + i, + this.exampleHeader[i], + this.code[i], + this.resources[i] + ); } } }; @@ -94,7 +119,12 @@ aria.widget.SourceCode.prototype.make = function () { * @method createCode * @memberof aria.widget.SourceCode */ -aria.widget.SourceCode.prototype.createCode = function (sourceCodeNode, node, indentLevel, skipFirst) { +aria.widget.SourceCode.prototype.createCode = function ( + sourceCodeNode, + node, + indentLevel, + skipFirst +) { if (!skipFirst) { var openTag = ''; var nodeNameStr = node.nodeName.toLowerCase(); @@ -109,9 +139,10 @@ aria.widget.SourceCode.prototype.createCode = function (sourceCodeNode, node, in } var attributeValue = sanitizeNodeValue(node.attributes[attrPos].value); - openTag += node.attributes[attrPos].nodeName + '="' + attributeValue + '"'; + openTag += + node.attributes[attrPos].nodeName + '="' + attributeValue + '"'; - if (wrapAttributes && (attrPos !== node.attributes.length - 1)) { + if (wrapAttributes && attrPos !== node.attributes.length - 1) { openTag += '
      ' + indentation(indentLevel); openTag += ' '.repeat(nodeNameStr.length + 2); } @@ -134,7 +165,10 @@ aria.widget.SourceCode.prototype.createCode = function (sourceCodeNode, node, in case Node.TEXT_NODE: if (hasText(childNode.nodeValue)) { var textNodeContent = sanitizeNodeValue(childNode.nodeValue).trim(); - textNodeContent = indentLines(textNodeContent, indentation(indentLevel)); + textNodeContent = indentLines( + textNodeContent, + indentation(indentLevel) + ); sourceCodeNode.innerHTML += '
      ' + textNodeContent; } @@ -144,7 +178,10 @@ aria.widget.SourceCode.prototype.createCode = function (sourceCodeNode, node, in if (hasText(childNode.nodeValue)) { var commentNodeContent = sanitizeNodeValue(childNode.nodeValue); commentNodeContent = '<!--' + commentNodeContent + '-->'; - commentNodeContent = indentLines(commentNodeContent, indentation(indentLevel)); + commentNodeContent = indentLines( + commentNodeContent, + indentation(indentLevel) + ); sourceCodeNode.innerHTML += '
      ' + commentNodeContent; } @@ -171,7 +208,7 @@ aria.widget.SourceCode.prototype.createCode = function (sourceCodeNode, node, in * @param {Number} indentLevel * @returns {String} */ -function indentation (indentLevel) { +function indentation(indentLevel) { return DEFAULT_INDENT.repeat(indentLevel); } @@ -193,7 +230,7 @@ function indentation (indentLevel) { * @param {String} nodeContent content of a node to test for whitespace characters * @returns {Boolean} */ -function hasText (nodeContent) { +function hasText(nodeContent) { if (typeof nodeContent !== 'string') { return false; } @@ -207,7 +244,7 @@ function hasText (nodeContent) { * @param {String} textContent * @returns {String} */ -function sanitizeNodeValue (textContent) { +function sanitizeNodeValue(textContent) { if (typeof textContent !== 'string') { return ''; } @@ -225,7 +262,7 @@ function sanitizeNodeValue (textContent) { * @param {String} textContent * @returns {String} */ -function stripIndentation (textContent) { +function stripIndentation(textContent) { var textIndentation = detectIndentation(textContent); if (textIndentation === 0) { @@ -273,7 +310,7 @@ function stripIndentation (textContent) { * @param {String} textContent * @returns {Number} The level of indentation */ -function detectIndentation (textContent) { +function detectIndentation(textContent) { // Case 1 if (!textContent.includes('\n')) { return 0; @@ -304,7 +341,7 @@ function detectIndentation (textContent) { * @param {String} indentation * @returns {String} */ -function indentLines (input, indentation) { +function indentLines(input, indentation) { var lines = input.split('\n'); lines = lines.map(function (line) { @@ -322,9 +359,14 @@ function indentLines (input, indentation) { * @param {String} exampleCodeId - the example html code * @param {String} exampleFilesId - the element containing all relevent CSS and JS file */ -function addOpenInCodePenForm (exampleIndex, exampleHeaderId, exampleCodeId, exampleFilesId) { +function addOpenInCodePenForm( + exampleIndex, + exampleHeaderId, + exampleCodeId, + exampleFilesId +) { var jsonInputId = 'codepen-data-ex-' + exampleIndex; - var buttonId = exampleCodeId + '-codepenbutton' + var buttonId = exampleCodeId + '-codepenbutton'; var form = document.createElement('form'); form.setAttribute('action', 'https://codepen.io/pen/define'); @@ -349,61 +391,62 @@ function addOpenInCodePenForm (exampleIndex, exampleHeaderId, exampleCodeId, exa var indentedExampleHtml = document.getElementById(exampleCodeId).innerHTML; indentedExampleHtml = indentedExampleHtml.replace(/^\n+/, ''); var indentation = indentedExampleHtml.match(/^\s+/)[0]; - var exampleHtml = indentedExampleHtml.replace(new RegExp('^' + indentation, 'gm'), ''); + var exampleHtml = indentedExampleHtml.replace( + new RegExp('^' + indentation, 'gm'), + '' + ); var postJson = { html: exampleHtml, css: '', js: '', - head: '' + head: '', }; var totalFetchedFiles = 0; var fileLinks = document.querySelectorAll('#' + exampleFilesId + ' a'); for (let fileLink of fileLinks) { - - var request = new XMLHttpRequest(); - - request.open('GET', fileLink.href, true); - request.onload = function() { - var href = this.responseURL; - if (this.status >= 200 && this.status < 400) { - if (href.indexOf('css') !== -1) { - postJson.css = postJson.css.concat(this.response); - } - if (href.indexOf('js') !== -1) { - postJson.js = postJson.js.concat(this.response); - } - totalFetchedFiles++; - } - else { - hideButton(buttonId, "Could not load resource: " + href); - } - }; - request.onerror = function() { - hideButton(buttonId, "Could not load resource: " + fileLink.href); - }; - request.send(); + var request = new XMLHttpRequest(); + + request.open('GET', fileLink.href, true); + request.onload = function () { + var href = this.responseURL; + if (this.status >= 200 && this.status < 400) { + if (href.indexOf('css') !== -1) { + postJson.css = postJson.css.concat(this.response); + } + if (href.indexOf('js') !== -1) { + postJson.js = postJson.js.concat(this.response); + } + totalFetchedFiles++; + } else { + hideButton(buttonId, 'Could not load resource: ' + href); + } + }; + request.onerror = function () { + hideButton(buttonId, 'Could not load resource: ' + fileLink.href); + }; + request.send(); } - var timerId = setInterval(() => { - console.log(totalFetchedFiles); - if (totalFetchedFiles === fileLinks.length) { - document.getElementById(jsonInputId).value = JSON.stringify(postJson); - clearInterval(timerId); - } - }, 500); + var timerId = setInterval(() => { + console.log(totalFetchedFiles); + if (totalFetchedFiles === fileLinks.length) { + document.getElementById(jsonInputId).value = JSON.stringify(postJson); + clearInterval(timerId); + } + }, 500); - setTimeout(() => { - clearInterval(timerId); - }, 10000); + setTimeout(() => { + clearInterval(timerId); + }, 10000); } function hideButton(buttonId, errorMsg) { - let button = document.querySelector(buttonId); - button.style.display = "none"; - console.log("Removing 'Open in Codepen button'. " + errorMsg); + let button = document.querySelector(buttonId); + button.style.display = 'none'; + console.log("Removing 'Open in Codepen button'. " + errorMsg); } var sourceCode = new aria.widget.SourceCode(); diff --git a/examples/js/utils.js b/examples/js/utils.js index 812d29cfd4..adb0e2b748 100644 --- a/examples/js/utils.js +++ b/examples/js/utils.js @@ -24,7 +24,7 @@ aria.KeyCode = { UP: 38, RIGHT: 39, DOWN: 40, - DELETE: 46 + DELETE: 46, }; aria.Utils = aria.Utils || {}; @@ -55,9 +55,11 @@ aria.Utils.remove = function (item) { if (item.remove && typeof item.remove === 'function') { return item.remove(); } - if (item.parentNode && - item.parentNode.removeChild && - typeof item.parentNode.removeChild === 'function') { + if ( + item.parentNode && + item.parentNode.removeChild && + typeof item.parentNode.removeChild === 'function' + ) { return item.parentNode.removeChild(item); } return false; @@ -98,8 +100,7 @@ aria.Utils.getAncestorBySelector = function (element, selector) { while (ancestor === null) { if (aria.Utils.matches(currentNode.parentNode, selector)) { ancestor = currentNode.parentNode; - } - else { + } else { currentNode = currentNode.parentNode; } } @@ -108,7 +109,7 @@ aria.Utils.getAncestorBySelector = function (element, selector) { }; aria.Utils.hasClass = function (element, className) { - return (new RegExp('(\\s|^)' + className + '(\\s|$)')).test(element.className); + return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className); }; aria.Utils.addClass = function (element, className) { diff --git a/examples/landmarks/js/show.js b/examples/landmarks/js/show.js index 71479746f6..042d41be93 100644 --- a/examples/landmarks/js/show.js +++ b/examples/landmarks/js/show.js @@ -17,28 +17,26 @@ 'use strict'; -function showLandmarks (event) { +function showLandmarks(event) { if (typeof window[initLandmarks] !== 'function') { window[initLandmarks] = initLandmarks(); } if (window[initLandmarks].run()) { event.target.innerHTML = 'Hide Landmarks'; - } - else { + } else { event.target.innerHTML = 'Show Landmarks'; } } -function showHeadings (event) { +function showHeadings(event) { if (typeof window[initHeadings] !== 'function') { window[initHeadings] = initHeadings(); } if (window[initHeadings].run()) { event.target.innerHTML = 'Hide Headings'; - } - else { + } else { event.target.innerHTML = 'Show Headings'; } } diff --git a/examples/link/js/link.js b/examples/link/js/link.js index 1bf9a14364..ecd1eba772 100644 --- a/examples/link/js/link.js +++ b/examples/link/js/link.js @@ -1,17 +1,14 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; -function goToLink (event, url) { +function goToLink(event, url) { var type = event.type; - if ( - (type === 'click') || - (type === 'keydown' && event.keyCode === 13) - ) { + if (type === 'click' || (type === 'keydown' && event.keyCode === 13)) { window.location.href = url; event.preventDefault(); diff --git a/examples/listbox/js/listbox-collapsible.js b/examples/listbox/js/listbox-collapsible.js index 8dc8750eb5..157f9fc10c 100644 --- a/examples/listbox/js/listbox-collapsible.js +++ b/examples/listbox/js/listbox-collapsible.js @@ -22,8 +22,14 @@ aria.ListboxButton = function (button, listbox) { aria.ListboxButton.prototype.registerEvents = function () { this.button.addEventListener('click', this.showListbox.bind(this)); this.button.addEventListener('keyup', this.checkShow.bind(this)); - this.listbox.listboxNode.addEventListener('blur', this.hideListbox.bind(this)); - this.listbox.listboxNode.addEventListener('keydown', this.checkHide.bind(this)); + this.listbox.listboxNode.addEventListener( + 'blur', + this.hideListbox.bind(this) + ); + this.listbox.listboxNode.addEventListener( + 'keydown', + this.checkHide.bind(this) + ); this.listbox.setHandleFocusChange(this.onFocusChange.bind(this)); }; diff --git a/examples/listbox/js/listbox-rearrangeable.js b/examples/listbox/js/listbox-rearrangeable.js index 74a62e9b46..d8b49aa855 100644 --- a/examples/listbox/js/listbox-rearrangeable.js +++ b/examples/listbox/js/listbox-rearrangeable.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -13,15 +13,22 @@ window.addEventListener('load', function () { var ex1 = document.getElementById('ex1'); - var ex1ImportantListbox = new aria.Listbox(document.getElementById('ss_imp_list')); - var ex1UnimportantListbox = new aria.Listbox(document.getElementById('ss_unimp_list')); + var ex1ImportantListbox = new aria.Listbox( + document.getElementById('ss_imp_list') + ); + var ex1UnimportantListbox = new aria.Listbox( + document.getElementById('ss_unimp_list') + ); new aria.Toolbar(ex1.querySelector('[role="toolbar"]')); ex1ImportantListbox.enableMoveUpDown( document.getElementById('ex1-up'), document.getElementById('ex1-down') ); - ex1ImportantListbox.setupMove(document.getElementById('ex1-delete'), ex1UnimportantListbox); + ex1ImportantListbox.setupMove( + document.getElementById('ex1-delete'), + ex1UnimportantListbox + ); ex1ImportantListbox.setHandleItemChange(function (event, items) { var updateText = ''; @@ -30,11 +37,15 @@ window.addEventListener('load', function () { updateText = 'Moved ' + items[0].innerText + ' to important features.'; break; case 'removed': - updateText = 'Moved ' + items[0].innerText + ' to unimportant features.'; + updateText = + 'Moved ' + items[0].innerText + ' to unimportant features.'; break; case 'moved_up': case 'moved_down': - var pos = Array.prototype.indexOf.call(this.listboxNode.children, items[0]); + var pos = Array.prototype.indexOf.call( + this.listboxNode.children, + items[0] + ); pos++; updateText = 'Moved to position ' + pos; break; @@ -45,13 +56,26 @@ window.addEventListener('load', function () { ex1LiveRegion.innerText = updateText; } }); - ex1UnimportantListbox.setupMove(document.getElementById('ex1-add'), ex1ImportantListbox); + ex1UnimportantListbox.setupMove( + document.getElementById('ex1-add'), + ex1ImportantListbox + ); - var ex2ImportantListbox = new aria.Listbox(document.getElementById('ms_imp_list')); - var ex2UnimportantListbox = new aria.Listbox(document.getElementById('ms_unimp_list')); + var ex2ImportantListbox = new aria.Listbox( + document.getElementById('ms_imp_list') + ); + var ex2UnimportantListbox = new aria.Listbox( + document.getElementById('ms_unimp_list') + ); - ex2ImportantListbox.setupMove(document.getElementById('ex2-add'), ex2UnimportantListbox); - ex2UnimportantListbox.setupMove(document.getElementById('ex2-delete'), ex2ImportantListbox); + ex2ImportantListbox.setupMove( + document.getElementById('ex2-add'), + ex2UnimportantListbox + ); + ex2UnimportantListbox.setupMove( + document.getElementById('ex2-delete'), + ex2ImportantListbox + ); ex2UnimportantListbox.setHandleItemChange(function (event, items) { var updateText = ''; var itemText = items.length === 1 ? '1 item' : items.length + ' items'; diff --git a/examples/listbox/js/listbox-scrollable.js b/examples/listbox/js/listbox-scrollable.js index 3479396146..fd7781e4b5 100644 --- a/examples/listbox/js/listbox-scrollable.js +++ b/examples/listbox/js/listbox-scrollable.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; diff --git a/examples/listbox/js/listbox.js b/examples/listbox/js/listbox.js index a7d9e89801..87c92b7b55 100644 --- a/examples/listbox/js/listbox.js +++ b/examples/listbox/js/listbox.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -21,7 +21,9 @@ var aria = aria || {}; */ aria.Listbox = function (listboxNode) { this.listboxNode = listboxNode; - this.activeDescendant = this.listboxNode.getAttribute('aria-activedescendant'); + this.activeDescendant = this.listboxNode.getAttribute( + 'aria-activedescendant' + ); this.multiselectable = this.listboxNode.hasAttribute('aria-multiselectable'); this.moveUpDownEnabled = false; this.siblingList = null; @@ -45,7 +47,10 @@ aria.Listbox.prototype.registerEvents = function () { this.listboxNode.addEventListener('click', this.checkClickItem.bind(this)); if (this.multiselectable) { - this.listboxNode.addEventListener('mousedown', this.checkMouseDown.bind(this)); + this.listboxNode.addEventListener( + 'mousedown', + this.checkMouseDown.bind(this) + ); } }; @@ -95,7 +100,8 @@ aria.Listbox.prototype.checkKeyPress = function (evt) { var key = evt.which || evt.keyCode; var lastActiveId = this.activeDescendant; var allOptions = this.listboxNode.querySelectorAll('[role="option"]'); - var currentItem = document.getElementById(this.activeDescendant) || allOptions[0]; + var currentItem = + document.getElementById(this.activeDescendant) || allOptions[0]; var nextItem = currentItem; if (!currentItem) { @@ -110,8 +116,7 @@ aria.Listbox.prototype.checkKeyPress = function (evt) { if (key === aria.KeyCode.PAGE_UP) { this.moveUpItems(); - } - else { + } else { this.moveDownItems(); } } @@ -119,7 +124,6 @@ aria.Listbox.prototype.checkKeyPress = function (evt) { break; case aria.KeyCode.UP: case aria.KeyCode.DOWN: - if (!this.activeDescendant) { // focus first option if no option was previously focused, and perform no other actions this.focusItem(currentItem); @@ -130,8 +134,7 @@ aria.Listbox.prototype.checkKeyPress = function (evt) { evt.preventDefault(); if (key === aria.KeyCode.UP) { this.moveUpItems(); - } - else { + } else { this.moveDownItems(); } return; @@ -139,8 +142,7 @@ aria.Listbox.prototype.checkKeyPress = function (evt) { if (key === aria.KeyCode.UP) { nextItem = this.findPreviousOption(currentItem); - } - else { + } else { nextItem = this.findNextOption(currentItem); } @@ -171,7 +173,7 @@ aria.Listbox.prototype.checkKeyPress = function (evt) { this.selectRange(this.startRangeIndex, allOptions.length - 1); } break; - case aria.KeyCode.SHIFT: + case aria.KeyCode.SHIFT: this.startRangeIndex = this.getElementIndex(currentItem, allOptions); break; case aria.KeyCode.SPACE: @@ -228,7 +230,7 @@ aria.Listbox.prototype.checkKeyPress = function (evt) { this.selectRange(0, allOptions.length - 1); break; } - // fall through + // fall through default: var itemToFocus = this.findItemToFocus(key); if (itemToFocus) { @@ -263,11 +265,7 @@ aria.Listbox.prototype.findItemToFocus = function (key) { itemList.length ); if (!nextMatch) { - nextMatch = this.findMatchInRange( - itemList, - 0, - searchIndex - ); + nextMatch = this.findMatchInRange(itemList, 0, searchIndex); } return nextMatch; }; @@ -282,7 +280,9 @@ aria.Listbox.prototype.getElementIndex = function (option, options) { /* Return the next listbox option, if it exists; otherwise, returns null */ aria.Listbox.prototype.findNextOption = function (currentOption) { - var allOptions = Array.prototype.slice.call(this.listboxNode.querySelectorAll('[role="option"]')); // get options array + var allOptions = Array.prototype.slice.call( + this.listboxNode.querySelectorAll('[role="option"]') + ); // get options array var currentOptionIndex = allOptions.indexOf(currentOption); var nextOption = null; @@ -295,7 +295,9 @@ aria.Listbox.prototype.findNextOption = function (currentOption) { /* Return the previous listbox option, if it exists; otherwise, returns null */ aria.Listbox.prototype.findPreviousOption = function (currentOption) { - var allOptions = Array.prototype.slice.call(this.listboxNode.querySelectorAll('[role="option"]')); // get options array + var allOptions = Array.prototype.slice.call( + this.listboxNode.querySelectorAll('[role="option"]') + ); // get options array var currentOptionIndex = allOptions.indexOf(currentOption); var previousOption = null; @@ -311,13 +313,20 @@ aria.Listbox.prototype.clearKeysSoFarAfterDelay = function () { clearTimeout(this.keyClear); this.keyClear = null; } - this.keyClear = setTimeout((function () { - this.keysSoFar = ''; - this.keyClear = null; - }).bind(this), 500); + this.keyClear = setTimeout( + function () { + this.keysSoFar = ''; + this.keyClear = null; + }.bind(this), + 500 + ); }; -aria.Listbox.prototype.findMatchInRange = function (list, startIndex, endIndex) { +aria.Listbox.prototype.findMatchInRange = function ( + list, + startIndex, + endIndex +) { // Find the first item starting with the keysSoFar substring, searching in // the specified range of items for (var n = startIndex; n < endIndex; n++) { @@ -354,7 +363,11 @@ aria.Listbox.prototype.checkClickItem = function (evt) { * Prevent text selection on shift + click for multiselect listboxes */ aria.Listbox.prototype.checkMouseDown = function (evt) { - if (this.multiselectable && evt.shiftKey && evt.target.getAttribute('role') === 'option') { + if ( + this.multiselectable && + evt.shiftKey && + evt.target.getAttribute('role') === 'option' + ) { evt.preventDefault(); } }; @@ -421,12 +434,12 @@ aria.Listbox.prototype.focusItem = function (element) { /** * Helper function to check if a number is within a range; no side effects. */ -aria.Listbox.prototype.checkInRange = function(index, start, end) { +aria.Listbox.prototype.checkInRange = function (index, start, end) { var rangeStart = start < end ? start : end; var rangeEnd = start < end ? end : start; return index >= rangeStart && index <= rangeEnd; -} +}; /** * Select a range of options @@ -434,8 +447,10 @@ aria.Listbox.prototype.checkInRange = function(index, start, end) { aria.Listbox.prototype.selectRange = function (start, end) { // get start/end indices var allOptions = this.listboxNode.querySelectorAll('[role="option"]'); - var startIndex = typeof start === 'number' ? start : this.getElementIndex(start, allOptions); - var endIndex = typeof end === 'number' ? end : this.getElementIndex(end, allOptions); + var startIndex = + typeof start === 'number' ? start : this.getElementIndex(start, allOptions); + var endIndex = + typeof end === 'number' ? end : this.getElementIndex(end, allOptions); for (var index = 0; index < allOptions.length; index++) { var selected = this.checkInRange(index, startIndex, endIndex); @@ -448,31 +463,34 @@ aria.Listbox.prototype.selectRange = function (start, end) { /** * Check for selected options and update moveButton, if applicable */ -aria.Listbox.prototype.updateMoveButton = function() { +aria.Listbox.prototype.updateMoveButton = function () { if (!this.moveButton) { return; } if (this.listboxNode.querySelector('[aria-selected="true"]')) { this.moveButton.setAttribute('aria-disabled', 'false'); - } - else { + } else { this.moveButton.setAttribute('aria-disabled', 'true'); } -} +}; /** * Check if the selected option is in view, and scroll if not */ aria.Listbox.prototype.updateScroll = function () { var selectedOption = document.getElementById(this.activeDescendant); - if (selectedOption && this.listboxNode.scrollHeight > this.listboxNode.clientHeight) { - var scrollBottom = this.listboxNode.clientHeight + this.listboxNode.scrollTop; + if ( + selectedOption && + this.listboxNode.scrollHeight > this.listboxNode.clientHeight + ) { + var scrollBottom = + this.listboxNode.clientHeight + this.listboxNode.scrollTop; var elementBottom = selectedOption.offsetTop + selectedOption.offsetHeight; if (elementBottom > scrollBottom) { - this.listboxNode.scrollTop = elementBottom - this.listboxNode.clientHeight; - } - else if (selectedOption.offsetTop < this.listboxNode.scrollTop) { + this.listboxNode.scrollTop = + elementBottom - this.listboxNode.clientHeight; + } else if (selectedOption.offsetTop < this.listboxNode.scrollTop) { this.listboxNode.scrollTop = selectedOption.offsetTop; } } @@ -498,8 +516,7 @@ aria.Listbox.prototype.checkUpDownButtons = function () { if (this.upButton) { if (activeElement.previousElementSibling) { this.upButton.setAttribute('aria-disabled', false); - } - else { + } else { this.upButton.setAttribute('aria-disabled', 'true'); } } @@ -507,8 +524,7 @@ aria.Listbox.prototype.checkUpDownButtons = function () { if (this.downButton) { if (activeElement.nextElementSibling) { this.downButton.setAttribute('aria-disabled', false); - } - else { + } else { this.downButton.setAttribute('aria-disabled', 'true'); } } @@ -526,11 +542,13 @@ aria.Listbox.prototype.addItems = function (items) { return false; } - items.forEach((function (item) { - this.defocusItem(item); - this.toggleSelectItem(item); - this.listboxNode.append(item); - }).bind(this)); + items.forEach( + function (item) { + this.defocusItem(item); + this.toggleSelectItem(item); + this.listboxNode.append(item); + }.bind(this) + ); if (!this.activeDescendant) { this.focusItem(items[0]); @@ -553,22 +571,23 @@ aria.Listbox.prototype.deleteItems = function () { if (this.multiselectable) { itemsToDelete = this.listboxNode.querySelectorAll('[aria-selected="true"]'); - } - else if (this.activeDescendant) { - itemsToDelete = [ document.getElementById(this.activeDescendant) ]; + } else if (this.activeDescendant) { + itemsToDelete = [document.getElementById(this.activeDescendant)]; } if (!itemsToDelete || !itemsToDelete.length) { return []; } - itemsToDelete.forEach((function (item) { - item.remove(); + itemsToDelete.forEach( + function (item) { + item.remove(); - if (item.id === this.activeDescendant) { - this.clearActiveDescendant(); - } - }).bind(this)); + if (item.id === this.activeDescendant) { + this.clearActiveDescendant(); + } + }.bind(this) + ); this.handleItemChange('removed', itemsToDelete); @@ -598,7 +617,7 @@ aria.Listbox.prototype.moveUpItems = function () { if (previousItem) { this.listboxNode.insertBefore(currentItem, previousItem); - this.handleItemChange('moved_up', [ currentItem ]); + this.handleItemChange('moved_up', [currentItem]); } this.checkUpDownButtons(); @@ -619,7 +638,7 @@ aria.Listbox.prototype.moveDownItems = function () { if (nextItem) { this.listboxNode.insertBefore(nextItem, currentItem); - this.handleItemChange('moved_down', [ currentItem ]); + this.handleItemChange('moved_down', [currentItem]); } this.checkUpDownButtons(); diff --git a/examples/listbox/js/toolbar.js b/examples/listbox/js/toolbar.js index 43155dea4f..9e90d9d789 100644 --- a/examples/listbox/js/toolbar.js +++ b/examples/listbox/js/toolbar.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -31,7 +31,10 @@ aria.Toolbar = function (toolbarNode) { * Register events for the toolbar interactions */ aria.Toolbar.prototype.registerEvents = function () { - this.toolbarNode.addEventListener('keydown', this.checkFocusChange.bind(this)); + this.toolbarNode.addEventListener( + 'keydown', + this.checkFocusChange.bind(this) + ); this.toolbarNode.addEventListener('click', this.checkClickItem.bind(this)); }; diff --git a/examples/menu-button/js/MenuItemAction.js b/examples/menu-button/js/MenuItemAction.js index 75422efb90..80af6074c5 100644 --- a/examples/menu-button/js/MenuItemAction.js +++ b/examples/menu-button/js/MenuItemAction.js @@ -1,47 +1,46 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: MenuItem.js -* -* Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: MenuItem.js + * + * Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor MenuItem -* -* @desc -* Wrapper object for a simple menu item in a popup menu -* -* @param domNode -* The DOM element node that serves as the menu item container. -* The menuObj PopupMenu is responsible for checking that it has -* requisite metadata, e.g. role="menuitem". -* -* @param menuObj -* The object that is a wrapper for the PopupMenu DOM element that -* contains the menu item DOM element. See PopupMenuAction.js -*/ + * @constructor MenuItem + * + * @desc + * Wrapper object for a simple menu item in a popup menu + * + * @param domNode + * The DOM element node that serves as the menu item container. + * The menuObj PopupMenu is responsible for checking that it has + * requisite metadata, e.g. role="menuitem". + * + * @param menuObj + * The object that is a wrapper for the PopupMenu DOM element that + * contains the menu item DOM element. See PopupMenuAction.js + */ var PopupMenuItem = function (domNode, popupMenuObj) { - - this.domNode = domNode; + this.domNode = domNode; this.popupMenu = popupMenuObj; this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + RETURN: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); }; @@ -52,13 +51,12 @@ PopupMenuItem.prototype.init = function () { this.domNode.setAttribute('role', 'menuitem'); } - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); }; /* EVENT HANDLERS */ @@ -67,11 +65,11 @@ PopupMenuItem.prototype.handleKeydown = function (event) { var flag = false, char = event.key; - function isPrintableCharacter (str) { + function isPrintableCharacter(str) { return str.length === 1 && str.match(/\S/); } - if (event.ctrlKey || event.altKey || event.metaKey) { + if (event.ctrlKey || event.altKey || event.metaKey) { return; } @@ -79,9 +77,7 @@ PopupMenuItem.prototype.handleKeydown = function (event) { if (isPrintableCharacter(char)) { this.popupMenu.setFocusByFirstCharacter(this, char); } - } - else { - + } else { switch (event.keyCode) { case this.keyCode.SPACE: flag = true; @@ -159,7 +155,6 @@ PopupMenuItem.prototype.handleBlur = function (event) { PopupMenuItem.prototype.handleMouseover = function (event) { this.popupMenu.hasHover = true; this.popupMenu.open(); - }; PopupMenuItem.prototype.handleMouseout = function (event) { diff --git a/examples/menu-button/js/MenuItemActionActivedescendant.js b/examples/menu-button/js/MenuItemActionActivedescendant.js index 599f7551d5..72dce15880 100644 --- a/examples/menu-button/js/MenuItemActionActivedescendant.js +++ b/examples/menu-button/js/MenuItemActionActivedescendant.js @@ -1,29 +1,29 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: MenuItem.js -* -* Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: MenuItem.js + * + * Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor MenuItem -* -* @desc -* Wrapper object for a simple menu item in a popup menu -* -* @param domNode -* The DOM element node that serves as the menu item container. -* The menuObj PopupMenu is responsible for checking that it has -* requisite metadata, e.g. role="menuitem". -* -* @param menuObj -* The object that is a wrapper for the PopupMenu DOM element that -* contains the menu item DOM element. See PopupMenuAction.js -*/ + * @constructor MenuItem + * + * @desc + * Wrapper object for a simple menu item in a popup menu + * + * @param domNode + * The DOM element node that serves as the menu item container. + * The menuObj PopupMenu is responsible for checking that it has + * requisite metadata, e.g. role="menuitem". + * + * @param menuObj + * The object that is a wrapper for the PopupMenu DOM element that + * contains the menu item DOM element. See PopupMenuAction.js + */ var MenuItem = function (domNode, menuObj) { this.domNode = domNode; this.menu = menuObj; @@ -36,9 +36,9 @@ MenuItem.prototype.init = function () { this.domNode.setAttribute('role', 'menuitem'); } - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); }; /* EVENT HANDLERS */ diff --git a/examples/menu-button/js/MenuItemLinks.js b/examples/menu-button/js/MenuItemLinks.js index d307d68f8c..c16af95165 100644 --- a/examples/menu-button/js/MenuItemLinks.js +++ b/examples/menu-button/js/MenuItemLinks.js @@ -1,47 +1,46 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: MenuItemLinks.js -* -* Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: MenuItemLinks.js + * + * Desc: Popup Menu Menuitem widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor MenuItemLinks -* -* @desc -* Wrapper object for a simple menu item in a popup menu -* -* @param domNode -* The DOM element node that serves as the menu item container. -* The menuObj PopupMenu is responsible for checking that it has -* requisite metadata, e.g. role="menuitem". -* -* @param menuObj -* The object that is a wrapper for the PopupMenu DOM element that -* contains the menu item DOM element. See PopupMenu.js -*/ + * @constructor MenuItemLinks + * + * @desc + * Wrapper object for a simple menu item in a popup menu + * + * @param domNode + * The DOM element node that serves as the menu item container. + * The menuObj PopupMenu is responsible for checking that it has + * requisite metadata, e.g. role="menuitem". + * + * @param menuObj + * The object that is a wrapper for the PopupMenu DOM element that + * contains the menu item DOM element. See PopupMenu.js + */ var MenuItemLinks = function (domNode, menuObj) { - this.domNode = domNode; this.menu = menuObj; this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + RETURN: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); }; @@ -52,13 +51,12 @@ MenuItemLinks.prototype.init = function () { this.domNode.setAttribute('role', 'menuitem'); } - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); }; /* EVENT HANDLERS */ @@ -67,11 +65,17 @@ MenuItemLinks.prototype.handleKeydown = function (event) { var flag = false, char = event.key; - function isPrintableCharacter (str) { + function isPrintableCharacter(str) { return str.length === 1 && str.match(/\S/); } - if (event.ctrlKey || event.altKey || event.metaKey || (event.keyCode === this.keyCode.SPACE) || (event.keyCode === this.keyCode.RETURN)) { + if ( + event.ctrlKey || + event.altKey || + event.metaKey || + event.keyCode === this.keyCode.SPACE || + event.keyCode === this.keyCode.RETURN + ) { return; } @@ -85,10 +89,8 @@ MenuItemLinks.prototype.handleKeydown = function (event) { this.menu.setFocusToController(); this.menu.close(true); } - } - else { + } else { switch (event.keyCode) { - case this.keyCode.ESC: this.menu.setFocusToController(); this.menu.close(true); @@ -153,7 +155,6 @@ MenuItemLinks.prototype.handleBlur = function (event) { MenuItemLinks.prototype.handleMouseover = function (event) { this.menu.hasHover = true; this.menu.open(); - }; MenuItemLinks.prototype.handleMouseout = function (event) { diff --git a/examples/menu-button/js/Menubutton.js b/examples/menu-button/js/Menubutton.js index fed9daa1e1..3b39b933f9 100644 --- a/examples/menu-button/js/Menubutton.js +++ b/examples/menu-button/js/Menubutton.js @@ -1,62 +1,60 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: Menubutton.js -* -* Desc: Menubutton widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: Menubutton.js + * + * Desc: Menubutton widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor MenuButton -* -* @desc -* Object that configures menu item elements by setting tabIndex -* and registering itself to handle pertinent events. -* -* While menuitem elements handle many keydown events, as well as -* focus and blur events, they do not maintain any state variables, -* delegating those responsibilities to its associated menu object. -* -* Consequently, it is only necessary to create one instance of -* MenubuttonItem from within the menu object; its configure method -* can then be called on each menuitem element. -* -* @param domNode -* The DOM element node that serves as the menu item container. -* The menuObj PopupMenu is responsible for checking that it has -* requisite metadata, e.g. role="menuitem". -* -* -*/ + * @constructor MenuButton + * + * @desc + * Object that configures menu item elements by setting tabIndex + * and registering itself to handle pertinent events. + * + * While menuitem elements handle many keydown events, as well as + * focus and blur events, they do not maintain any state variables, + * delegating those responsibilities to its associated menu object. + * + * Consequently, it is only necessary to create one instance of + * MenubuttonItem from within the menu object; its configure method + * can then be called on each menuitem element. + * + * @param domNode + * The DOM element node that serves as the menu item container. + * The menuObj PopupMenu is responsible for checking that it has + * requisite metadata, e.g. role="menuitem". + * + * + */ var Menubutton = function (domNode) { - - this.domNode = domNode; + this.domNode = domNode; this.popupMenu = false; this.hasFocus = false; this.hasHover = false; this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + RETURN: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); }; Menubutton.prototype.init = function () { - this.domNode.setAttribute('aria-haspopup', 'true'); this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); @@ -68,19 +66,19 @@ Menubutton.prototype.init = function () { // initialize pop up menus - var popupMenu = document.getElementById(this.domNode.getAttribute('aria-controls')); + var popupMenu = document.getElementById( + this.domNode.getAttribute('aria-controls') + ); if (popupMenu) { if (popupMenu.getAttribute('aria-activedescendant')) { this.popupMenu = new PopupMenuActionActivedescendant(popupMenu, this); this.popupMenu.init(); - } - else { + } else { this.popupMenu = new PopupMenuAction(popupMenu, this); this.popupMenu.init(); } } - }; Menubutton.prototype.handleKeydown = function (event) { @@ -118,8 +116,7 @@ Menubutton.prototype.handleKeydown = function (event) { Menubutton.prototype.handleClick = function (event) { if (this.domNode.getAttribute('aria-expanded') == 'true') { this.popupMenu.close(true); - } - else { + } else { this.popupMenu.open(); this.popupMenu.setFocusToFirstItem(); } @@ -132,7 +129,6 @@ Menubutton.prototype.handleFocus = function (event) { Menubutton.prototype.handleBlur = function (event) { this.popupMenu.hasFocus = false; setTimeout(this.popupMenu.close.bind(this.popupMenu, false), 300); - }; Menubutton.prototype.handleMouseover = function (event) { diff --git a/examples/menu-button/js/Menubutton2.js b/examples/menu-button/js/Menubutton2.js index 23dad838a4..5f7a082aad 100644 --- a/examples/menu-button/js/Menubutton2.js +++ b/examples/menu-button/js/Menubutton2.js @@ -1,79 +1,78 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: Menubutton.js -* -* Desc: Menubutton Menuitem widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: Menubutton.js + * + * Desc: Menubutton Menuitem widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor MenubuttonItem -* -* @desc -* Object that configures menu item elements by setting tabIndex -* and registering itself to handle pertinent events. -* -* While menuitem elements handle many keydown events, as well as -* focus and blur events, they do not maintain any state variables, -* delegating those responsibilities to its associated menu object. -* -* Consequently, it is only necessary to create one instance of -* MenubuttonItem from within the menu object; its configure method -* can then be called on each menuitem element. -* -* @param domNode -* The DOM element node that serves as the menu item container. -* The menuObj PopupMenu is responsible for checking that it has -* requisite metadata, e.g. role="menuitem". -* -*/ + * @constructor MenubuttonItem + * + * @desc + * Object that configures menu item elements by setting tabIndex + * and registering itself to handle pertinent events. + * + * While menuitem elements handle many keydown events, as well as + * focus and blur events, they do not maintain any state variables, + * delegating those responsibilities to its associated menu object. + * + * Consequently, it is only necessary to create one instance of + * MenubuttonItem from within the menu object; its configure method + * can then be called on each menuitem element. + * + * @param domNode + * The DOM element node that serves as the menu item container. + * The menuObj PopupMenu is responsible for checking that it has + * requisite metadata, e.g. role="menuitem". + * + */ var Menubutton = function (domNode) { - - this.domNode = domNode; + this.domNode = domNode; this.popupMenu = false; this.hasFocus = false; this.hasHover = false; this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + RETURN: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); }; Menubutton.prototype.init = function () { - this.domNode.setAttribute('aria-haspopup', 'true'); - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); // initialize pop up menus - var popupMenu = document.getElementById(this.domNode.getAttribute('aria-controls')); + var popupMenu = document.getElementById( + this.domNode.getAttribute('aria-controls') + ); if (popupMenu) { this.popupMenu = new PopupMenuLinks(popupMenu, this); this.popupMenu.init(); } - }; Menubutton.prototype.handleKeydown = function (event) { @@ -111,8 +110,7 @@ Menubutton.prototype.handleKeydown = function (event) { Menubutton.prototype.handleClick = function (event) { if (this.domNode.getAttribute('aria-expanded') == 'true') { this.popupMenu.close(true); - } - else { + } else { this.popupMenu.open(); this.popupMenu.setFocusToFirstItem(); } diff --git a/examples/menu-button/js/PopupMenuAction.js b/examples/menu-button/js/PopupMenuAction.js index 025d40c92d..b660e10ff8 100644 --- a/examples/menu-button/js/PopupMenuAction.js +++ b/examples/menu-button/js/PopupMenuAction.js @@ -1,37 +1,37 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: PopupMenuAction.js -* -* Desc: Popup menu widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: PopupMenuAction.js + * + * Desc: Popup menu widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor PopupMenuAction -* -* @desc -* Wrapper object for a simple popup menu (without nested submenus) -* -* @param domNode -* The DOM element node that serves as the popup menu container. Each -* child element of domNode that represents a menuitem must have a -* 'role' attribute with value 'menuitem'. -* -* @param controllerObj -* The object that is a wrapper for the DOM element that controls the -* menu, e.g. a button element, with an 'aria-controls' attribute that -* references this menu's domNode. See MenuButton.js -* -* The controller object is expected to have the following properties: -* 1. domNode: The controller object's DOM element node, needed for -* retrieving positioning information. -* 2. hasHover: boolean that indicates whether the controller object's -* domNode has responded to a mouseover event with no subsequent -* mouseout event having occurred. -*/ + * @constructor PopupMenuAction + * + * @desc + * Wrapper object for a simple popup menu (without nested submenus) + * + * @param domNode + * The DOM element node that serves as the popup menu container. Each + * child element of domNode that represents a menuitem must have a + * 'role' attribute with value 'menuitem'. + * + * @param controllerObj + * The object that is a wrapper for the DOM element that controls the + * menu, e.g. a button element, with an 'aria-controls' attribute that + * references this menu's domNode. See MenuButton.js + * + * The controller object is expected to have the following properties: + * 1. domNode: The controller object's DOM element node, needed for + * retrieving positioning information. + * 2. hasHover: boolean that indicates whether the controller object's + * domNode has responded to a mouseover event with no subsequent + * mouseout event having occurred. + */ var PopupMenuAction = function (domNode, controllerObj) { var elementChildren, msgPrefix = 'PopupMenu constructor argument domNode '; @@ -51,7 +51,9 @@ var PopupMenuAction = function (domNode, controllerObj) { while (childElement) { var menuitem = childElement.firstElementChild; if (menuitem && menuitem === 'A') { - throw new Error(msgPrefix + 'Cannot have descendant elements are A elements.'); + throw new Error( + msgPrefix + 'Cannot have descendant elements are A elements.' + ); } childElement = childElement.nextElementSibling; } @@ -59,49 +61,61 @@ var PopupMenuAction = function (domNode, controllerObj) { this.domNode = domNode; this.controller = controllerObj; - this.menuitems = []; // see PopupMenu init method - this.firstChars = []; // see PopupMenu init method + this.menuitems = []; // see PopupMenu init method + this.firstChars = []; // see PopupMenu init method - this.firstItem = null; // see PopupMenu init method - this.lastItem = null; // see PopupMenu init method + this.firstItem = null; // see PopupMenu init method + this.lastItem = null; // see PopupMenu init method - this.hasFocus = false; // see MenuItem handleFocus, handleBlur - this.hasHover = false; // see PopupMenu handleMouseover, handleMouseout + this.hasFocus = false; // see MenuItem handleFocus, handleBlur + this.hasHover = false; // see PopupMenu handleMouseover, handleMouseout }; /* -* @method PopupMenuAction.prototype.init -* -* @desc -* Add domNode event listeners for mouseover and mouseout. Traverse -* domNode children to configure each menuitem and populate menuitems -* array. Initialize firstItem and lastItem properties. -*/ + * @method PopupMenuAction.prototype.init + * + * @desc + * Add domNode event listeners for mouseover and mouseout. Traverse + * domNode children to configure each menuitem and populate menuitems + * array. Initialize firstItem and lastItem properties. + */ PopupMenuAction.prototype.init = function () { - var childElement, menuElement, firstChildElement, menuItem, textContent, numItems, label; + var childElement, + menuElement, + firstChildElement, + menuItem, + textContent, + numItems, + label; // Configure the domNode itself this.domNode.tabIndex = -1; this.domNode.setAttribute('role', 'menu'); - if (!this.domNode.getAttribute('aria-labelledby') && !this.domNode.getAttribute('aria-label') && !this.domNode.getAttribute('title')) { + if ( + !this.domNode.getAttribute('aria-labelledby') && + !this.domNode.getAttribute('aria-label') && + !this.domNode.getAttribute('title') + ) { label = this.controller.domNode.innerHTML; this.domNode.setAttribute('aria-label', label); } this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); // Traverse the element children of domNode: configure each with // menuitem role behavior and store reference in menuitems array. var menuElements = this.domNode.getElementsByTagName('LI'); for (var i = 0; i < menuElements.length; i++) { - menuElement = menuElements[i]; - if (!menuElement.firstElementChild && menuElement.getAttribute('role') != 'separator') { + if ( + !menuElement.firstElementChild && + menuElement.getAttribute('role') != 'separator' + ) { menuItem = new PopupMenuItem(menuElement, this); menuItem.init(); this.menuitems.push(menuItem); @@ -114,7 +128,7 @@ PopupMenuAction.prototype.init = function () { numItems = this.menuitems.length; if (numItems > 0) { this.firstItem = this.menuitems[0]; - this.lastItem = this.menuitems[numItems - 1]; + this.lastItem = this.menuitems[numItems - 1]; } }; @@ -138,12 +152,10 @@ PopupMenuAction.prototype.setFocusToController = function (command) { if (command === 'previous') { this.controller.menubutton.setFocusToPreviousItem(this.controller); - } - else { + } else { if (command === 'next') { this.controller.menubutton.setFocusToNextItem(this.controller); - } - else { + } else { this.controller.domNode.focus(); } } @@ -162,8 +174,7 @@ PopupMenuAction.prototype.setFocusToPreviousItem = function (currentItem) { if (currentItem === this.firstItem) { this.lastItem.domNode.focus(); - } - else { + } else { index = this.menuitems.indexOf(currentItem); this.menuitems[index - 1].domNode.focus(); } @@ -174,14 +185,16 @@ PopupMenuAction.prototype.setFocusToNextItem = function (currentItem) { if (currentItem === this.lastItem) { this.firstItem.domNode.focus(); - } - else { + } else { index = this.menuitems.indexOf(currentItem); this.menuitems[index + 1].domNode.focus(); } }; -PopupMenuAction.prototype.setFocusByFirstCharacter = function (currentItem, char) { +PopupMenuAction.prototype.setFocusByFirstCharacter = function ( + currentItem, + char +) { var start, index; char = char.toLowerCase(); @@ -224,7 +237,7 @@ PopupMenuAction.prototype.open = function () { // set CSS properties this.domNode.style.display = 'block'; this.domNode.style.position = 'absolute'; - this.domNode.style.top = rect.height + 'px'; + this.domNode.style.top = rect.height + 'px'; this.domNode.style.left = '0px'; // set aria-expanded attribute @@ -236,7 +249,10 @@ PopupMenuAction.prototype.close = function (force) { force = false; } - if (force || (!this.hasFocus && !this.hasHover && !this.controller.hasHover)) { + if ( + force || + (!this.hasFocus && !this.hasHover && !this.controller.hasHover) + ) { this.domNode.style.display = 'none'; this.controller.domNode.removeAttribute('aria-expanded'); } diff --git a/examples/menu-button/js/PopupMenuActionActivedescendant.js b/examples/menu-button/js/PopupMenuActionActivedescendant.js index eb07f59610..c31acbb9ce 100644 --- a/examples/menu-button/js/PopupMenuActionActivedescendant.js +++ b/examples/menu-button/js/PopupMenuActionActivedescendant.js @@ -1,37 +1,37 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: PopupMenuActionActivedescendant.js -* -* Desc: Popup menu widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: PopupMenuActionActivedescendant.js + * + * Desc: Popup menu widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor PopupMenuActionActivedescendant -* -* @desc -* Wrapper object for a simple popup menu (without nested submenus) -* -* @param domNode -* The DOM element node that serves as the popup menu container. Each -* child element of domNode that represents a menuitem must have a -* 'role' attribute with value 'menuitem'. -* -* @param controllerObj -* The object that is a wrapper for the DOM element that controls the -* menu, e.g. a button element, with an 'aria-controls' attribute that -* references this menu's domNode. See MenuButton.js -* -* The controller object is expected to have the following properties: -* 1. domNode: The controller object's DOM element node, needed for -* retrieving positioning information. -* 2. hasHover: boolean that indicates whether the controller object's -* domNode has responded to a mouseover event with no subsequent -* mouseout event having occurred. -*/ + * @constructor PopupMenuActionActivedescendant + * + * @desc + * Wrapper object for a simple popup menu (without nested submenus) + * + * @param domNode + * The DOM element node that serves as the popup menu container. Each + * child element of domNode that represents a menuitem must have a + * 'role' attribute with value 'menuitem'. + * + * @param controllerObj + * The object that is a wrapper for the DOM element that controls the + * menu, e.g. a button element, with an 'aria-controls' attribute that + * references this menu's domNode. See MenuButton.js + * + * The controller object is expected to have the following properties: + * 1. domNode: The controller object's DOM element node, needed for + * retrieving positioning information. + * 2. hasHover: boolean that indicates whether the controller object's + * domNode has responded to a mouseover event with no subsequent + * mouseout event having occurred. + */ var PopupMenuActionActivedescendant = function (domNode, controllerObj) { var elementChildren, msgPrefix = 'PopupMenu constructor argument domNode '; @@ -51,7 +51,9 @@ var PopupMenuActionActivedescendant = function (domNode, controllerObj) { while (childElement) { var menuitem = childElement.firstElementChild; if (menuitem && menuitem === 'A') { - throw new Error(msgPrefix + 'Cannot have descendant elements are A elements.'); + throw new Error( + msgPrefix + 'Cannot have descendant elements are A elements.' + ); } childElement = childElement.nextElementSibling; } @@ -61,55 +63,63 @@ var PopupMenuActionActivedescendant = function (domNode, controllerObj) { this.domNode = domNode; this.controller = controllerObj; - this.menuitems = []; // see PopupMenu init method - this.firstChars = []; // see PopupMenu init method + this.menuitems = []; // see PopupMenu init method + this.firstChars = []; // see PopupMenu init method - this.firstItem = null; // see PopupMenu init method - this.lastItem = null; // see PopupMenu init method + this.firstItem = null; // see PopupMenu init method + this.lastItem = null; // see PopupMenu init method - this.hasFocus = false; // see MenuItem handleFocus, handleBlur - this.hasHover = false; // see PopupMenu handleMouseover, handleMouseout + this.hasFocus = false; // see MenuItem handleFocus, handleBlur + this.hasHover = false; // see PopupMenu handleMouseover, handleMouseout this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + RETURN: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); - }; /* -* @method PopupMenuActionActivedescendant.prototype.init -* -* @desc -* Add domNode event listeners for mouseover and mouseout. Traverse -* domNode children to configure each menuitem and populate menuitems -* array. Initialize firstItem and lastItem properties. -*/ + * @method PopupMenuActionActivedescendant.prototype.init + * + * @desc + * Add domNode event listeners for mouseover and mouseout. Traverse + * domNode children to configure each menuitem and populate menuitems + * array. Initialize firstItem and lastItem properties. + */ PopupMenuActionActivedescendant.prototype.init = function () { - var childElement, menuElement, firstChildElement, menuItem, textContent, label; + var childElement, + menuElement, + firstChildElement, + menuItem, + textContent, + label; // Configure the domNode itself this.domNode.tabIndex = 0; this.domNode.setAttribute('role', 'menu'); - if (!this.domNode.getAttribute('aria-labelledby') && !this.domNode.getAttribute('aria-label') && !this.domNode.getAttribute('title')) { + if ( + !this.domNode.getAttribute('aria-labelledby') && + !this.domNode.getAttribute('aria-label') && + !this.domNode.getAttribute('title') + ) { label = this.controller.domNode.innerHTML; this.domNode.setAttribute('aria-label', label); } - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); this.domNode.addEventListener('blur', this.handleBlur.bind(this)); // Traverse the element children of domNode: configure each with @@ -117,9 +127,11 @@ PopupMenuActionActivedescendant.prototype.init = function () { var menuElements = this.domNode.getElementsByTagName('LI'); for (var i = 0; i < menuElements.length; i++) { - menuElement = menuElements[i]; - if (!menuElement.firstElementChild && menuElement.getAttribute('role') != 'separator') { + if ( + !menuElement.firstElementChild && + menuElement.getAttribute('role') != 'separator' + ) { menuItem = new MenuItem(menuElement, this); menuItem.init(); this.menuitems.push(menuItem); @@ -132,20 +144,19 @@ PopupMenuActionActivedescendant.prototype.init = function () { if (this.menuitems.length > 0) { this.firstItem = this.menuitems[0]; this.currentItem = this.firstItem; - this.lastItem = this.menuitems[this.menuitems.length - 1]; + this.lastItem = this.menuitems[this.menuitems.length - 1]; } - }; PopupMenuActionActivedescendant.prototype.handleKeydown = function (event) { var flag = false, char = event.key, clickEvent; - function isPrintableCharacter (str) { + function isPrintableCharacter(str) { return str.length === 1 && str.match(/\S/); } - if (event.ctrlKey || event.altKey || event.metaKey) { + if (event.ctrlKey || event.altKey || event.metaKey) { return; } @@ -153,10 +164,8 @@ PopupMenuActionActivedescendant.prototype.handleKeydown = function (event) { if (isPrintableCharacter(char)) { this.setFocusByFirstCharacter(char); } - } - else { + } else { switch (event.keyCode) { - case this.keyCode.SPACE: flag = true; break; @@ -166,12 +175,11 @@ PopupMenuActionActivedescendant.prototype.handleKeydown = function (event) { // and let the event handler handleClick do the housekeeping. try { clickEvent = new MouseEvent('click', { - 'view': window, - 'bubbles': true, - 'cancelable': true + view: window, + bubbles: true, + cancelable: true, }); - } - catch (err) { + } catch (err) { if (document.createEvent) { // DOM Level 3 for IE 9+ clickEvent = document.createEvent('MouseEvents'); @@ -268,8 +276,7 @@ PopupMenuActionActivedescendant.prototype.setFocusToPreviousItem = function () { if (this.currentItem === this.firstItem) { this.setFocusToLastItem(); - } - else { + } else { index = this.menuitems.indexOf(this.currentItem); this.setFocus(this.menuitems[index - 1]); } @@ -280,14 +287,15 @@ PopupMenuActionActivedescendant.prototype.setFocusToNextItem = function () { if (this.currentItem === this.lastItem) { this.setFocusToFirstItem(); - } - else { + } else { index = this.menuitems.indexOf(this.currentItem); this.setFocus(this.menuitems[index + 1]); } }; -PopupMenuActionActivedescendant.prototype.setFocusByFirstCharacter = function (char) { +PopupMenuActionActivedescendant.prototype.setFocusByFirstCharacter = function ( + char +) { var start, index; char = char.toLowerCase(); @@ -312,7 +320,10 @@ PopupMenuActionActivedescendant.prototype.setFocusByFirstCharacter = function (c } }; -PopupMenuActionActivedescendant.prototype.getIndexFirstChars = function (startIndex, char) { +PopupMenuActionActivedescendant.prototype.getIndexFirstChars = function ( + startIndex, + char +) { for (var i = startIndex; i < this.firstChars.length; i++) { if (char === this.firstChars[i]) { return i; @@ -324,7 +335,10 @@ PopupMenuActionActivedescendant.prototype.getIndexFirstChars = function (startIn PopupMenuActionActivedescendant.prototype.getCurrentItem = function () { var id = this.domNode.getAttribute('aria-activedescendant'); if (!id) { - this.domNode.setAttribute('aria-activedescendant', this.firstItem.domNode.id); + this.domNode.setAttribute( + 'aria-activedescendant', + this.firstItem.domNode.id + ); return this.firstItem; } for (var i = 0; i < this.menuitems.length; i++) { @@ -346,7 +360,7 @@ PopupMenuActionActivedescendant.prototype.open = function () { // set CSS properties this.domNode.style.display = 'block'; this.domNode.style.position = 'absolute'; - this.domNode.style.top = rect.height + 'px'; + this.domNode.style.top = rect.height + 'px'; this.domNode.style.left = '0px'; this.hasFocus = true; @@ -357,7 +371,6 @@ PopupMenuActionActivedescendant.prototype.open = function () { }; PopupMenuActionActivedescendant.prototype.close = function (force) { - if (typeof force !== 'boolean') { force = false; } diff --git a/examples/menu-button/js/PopupMenuLinks.js b/examples/menu-button/js/PopupMenuLinks.js index f8a5fb82d5..ca898dfd3c 100644 --- a/examples/menu-button/js/PopupMenuLinks.js +++ b/examples/menu-button/js/PopupMenuLinks.js @@ -1,37 +1,37 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: PopupMenuLinks.js -* -* Desc: Popup menu Links widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: PopupMenuLinks.js + * + * Desc: Popup menu Links widget that implements ARIA Authoring Practices + */ 'use strict'; /* -* @constructor PopupMenuLinks -* -* @desc -* Wrapper object for a simple popup menu (without nested submenus) -* -* @param domNode -* The DOM element node that serves as the popup menu container. Each -* child element of domNode that represents a menuitem must have a -* 'role' attribute with value 'menuitem'. -* -* @param controllerObj -* The object that is a wrapper for the DOM element that controls the -* menu, e.g. a button element, with an 'aria-controls' attribute that -* references this menu's domNode. See MenuButton.js -* -* The controller object is expected to have the following properties: -* 1. domNode: The controller object's DOM element node, needed for -* retrieving positioning information. -* 2. hasHover: boolean that indicates whether the controller object's -* domNode has responded to a mouseover event with no subsequent -* mouseout event having occurred. -*/ + * @constructor PopupMenuLinks + * + * @desc + * Wrapper object for a simple popup menu (without nested submenus) + * + * @param domNode + * The DOM element node that serves as the popup menu container. Each + * child element of domNode that represents a menuitem must have a + * 'role' attribute with value 'menuitem'. + * + * @param controllerObj + * The object that is a wrapper for the DOM element that controls the + * menu, e.g. a button element, with an 'aria-controls' attribute that + * references this menu's domNode. See MenuButton.js + * + * The controller object is expected to have the following properties: + * 1. domNode: The controller object's DOM element node, needed for + * retrieving positioning information. + * 2. hasHover: boolean that indicates whether the controller object's + * domNode has responded to a mouseover event with no subsequent + * mouseout event having occurred. + */ var PopupMenuLinks = function (domNode, controllerObj) { var elementChildren, msgPrefix = 'PopupMenuLinks constructor argument domNode '; @@ -51,7 +51,9 @@ var PopupMenuLinks = function (domNode, controllerObj) { while (childElement) { var menuitem = childElement.firstElementChild; if (menuitem && menuitem.tagName !== 'A') { - throw new Error(msgPrefix + 'has descendant elements that are not A elements.'); + throw new Error( + msgPrefix + 'has descendant elements that are not A elements.' + ); } childElement = childElement.nextElementSibling; } @@ -59,24 +61,24 @@ var PopupMenuLinks = function (domNode, controllerObj) { this.domNode = domNode; this.controller = controllerObj; - this.menuitems = []; // see PopupMenuLinks init method - this.firstChars = []; // see PopupMenuLinks init method + this.menuitems = []; // see PopupMenuLinks init method + this.firstChars = []; // see PopupMenuLinks init method - this.firstItem = null; // see PopupMenuLinks init method - this.lastItem = null; // see PopupMenuLinks init method + this.firstItem = null; // see PopupMenuLinks init method + this.lastItem = null; // see PopupMenuLinks init method - this.hasFocus = false; // see MenuItemLinks handleFocus, handleBlur - this.hasHover = false; // see PopupMenuLinks handleMouseover, handleMouseout + this.hasFocus = false; // see MenuItemLinks handleFocus, handleBlur + this.hasHover = false; // see PopupMenuLinks handleMouseover, handleMouseout }; /* -* @method PopupMenuLinks.prototype.init -* -* @desc -* Add domNode event listeners for mouseover and mouseout. Traverse -* domNode children to configure each menuitem and populate menuitems -* array. Initialize firstItem and lastItem properties. -*/ + * @method PopupMenuLinks.prototype.init + * + * @desc + * Add domNode event listeners for mouseover and mouseout. Traverse + * domNode children to configure each menuitem and populate menuitems + * array. Initialize firstItem and lastItem properties. + */ PopupMenuLinks.prototype.init = function () { var childElement, menuElement, menuItem, textContent, numItems, label; @@ -85,13 +87,17 @@ PopupMenuLinks.prototype.init = function () { this.domNode.setAttribute('role', 'menu'); - if (!this.domNode.getAttribute('aria-labelledby') && !this.domNode.getAttribute('aria-label') && !this.domNode.getAttribute('title')) { + if ( + !this.domNode.getAttribute('aria-labelledby') && + !this.domNode.getAttribute('aria-label') && + !this.domNode.getAttribute('title') + ) { label = this.controller.domNode.innerHTML; this.domNode.setAttribute('aria-label', label); } this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); // Traverse the element children of domNode: configure each with // menuitem role behavior and store reference in menuitems array. @@ -114,7 +120,7 @@ PopupMenuLinks.prototype.init = function () { numItems = this.menuitems.length; if (numItems > 0) { this.firstItem = this.menuitems[0]; - this.lastItem = this.menuitems[numItems - 1]; + this.lastItem = this.menuitems[numItems - 1]; } }; @@ -138,12 +144,10 @@ PopupMenuLinks.prototype.setFocusToController = function (command) { if (command === 'previous') { this.controller.menubar.setFocusToPreviousItem(this.controller); - } - else { + } else { if (command === 'next') { this.controller.menubar.setFocusToNextItem(this.controller); - } - else { + } else { this.controller.domNode.focus(); } } @@ -162,8 +166,7 @@ PopupMenuLinks.prototype.setFocusToPreviousItem = function (currentItem) { if (currentItem === this.firstItem) { this.lastItem.domNode.focus(); - } - else { + } else { index = this.menuitems.indexOf(currentItem); this.menuitems[index - 1].domNode.focus(); } @@ -174,14 +177,16 @@ PopupMenuLinks.prototype.setFocusToNextItem = function (currentItem) { if (currentItem === this.lastItem) { this.firstItem.domNode.focus(); - } - else { + } else { index = this.menuitems.indexOf(currentItem); this.menuitems[index + 1].domNode.focus(); } }; -PopupMenuLinks.prototype.setFocusByFirstCharacter = function (currentItem, char) { +PopupMenuLinks.prototype.setFocusByFirstCharacter = function ( + currentItem, + char +) { var start, index; char = char.toLowerCase(); @@ -224,7 +229,7 @@ PopupMenuLinks.prototype.open = function () { // set CSS properties this.domNode.style.display = 'block'; this.domNode.style.position = 'absolute'; - this.domNode.style.top = rect.height + 'px'; + this.domNode.style.top = rect.height + 'px'; this.domNode.style.left = '0px'; // set aria-expanded attribute @@ -232,8 +237,10 @@ PopupMenuLinks.prototype.open = function () { }; PopupMenuLinks.prototype.close = function (force) { - - if (force || (!this.hasFocus && !this.hasHover && !this.controller.hasHover)) { + if ( + force || + (!this.hasFocus && !this.hasHover && !this.controller.hasHover) + ) { this.domNode.style.display = 'none'; this.controller.domNode.removeAttribute('aria-expanded'); } diff --git a/examples/menubar/js/menubar-editor.js b/examples/menubar/js/menubar-editor.js index 3f26973421..905b4ce87a 100644 --- a/examples/menubar/js/menubar-editor.js +++ b/examples/menubar/js/menubar-editor.js @@ -1,16 +1,15 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: menubar-editor.js -* -* Desc: Creates a menubar to control the styling of text in a textarea element -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: menubar-editor.js + * + * Desc: Creates a menubar to control the styling of text in a textarea element + */ 'use strict'; var MenubarEditor = function (domNode) { - this.domNode = domNode; this.menubarNode = domNode.querySelector('[role=menubar]'); this.textareaNode = domNode.querySelector('textarea'); @@ -25,14 +24,18 @@ var MenubarEditor = function (domNode) { this.firstMenuitem = {}; // see Menubar init method this.lastMenuitem = {}; // see Menubar init method - this.initMenu(this.menubarNode) + this.initMenu(this.menubarNode); this.domNode.addEventListener('focusin', this.handleFocusin.bind(this)); this.domNode.addEventListener('focusout', this.handleFocusout.bind(this)); - window.addEventListener('mousedown', this.handleBackgroundMousedown.bind(this), true); + window.addEventListener( + 'mousedown', + this.handleBackgroundMousedown.bind(this), + true + ); }; -MenubarEditor.prototype.getMenuitems = function(domNode) { +MenubarEditor.prototype.getMenuitems = function (domNode) { var nodes = []; var initMenu = this.initMenu.bind(this); @@ -102,7 +105,7 @@ MenubarEditor.prototype.initMenu = function (menu) { this.firstMenuitem[menuId] = null; this.lastMenuitem[menuId] = null; - for(i = 0; i < menuitems.length; i++) { + for (i = 0; i < menuitems.length; i++) { menuitem = menuitems[i]; role = menuitem.getAttribute('role'); @@ -117,23 +120,24 @@ MenubarEditor.prototype.initMenu = function (menu) { menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); - menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); + menuitem.addEventListener( + 'mouseover', + this.handleMenuitemMouseover.bind(this) + ); - if( !this.firstMenuitem[menuId]) { + if (!this.firstMenuitem[menuId]) { if (this.hasPopup(menuitem)) { menuitem.tabIndex = 0; } this.firstMenuitem[menuId] = menuitem; } this.lastMenuitem[menuId] = menuitem; - } }; /* MenubarEditor FOCUS MANAGEMENT METHODS */ MenubarEditor.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { - var isAnyPopupOpen = this.isAnyPopupOpen(); this.closePopupAll(newMenuitem); @@ -142,8 +146,7 @@ MenubarEditor.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { if (isAnyPopupOpen) { this.openPopup(newMenuitem); } - } - else { + } else { var menu = this.getMenu(newMenuitem); var cmi = menu.previousElementSibling; if (!this.isOpen(cmi)) { @@ -153,7 +156,7 @@ MenubarEditor.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { if (this.hasPopup(newMenuitem)) { if (this.menuitemGroups[menuId]) { - this.menuitemGroups[menuId].forEach(function(item) { + this.menuitemGroups[menuId].forEach(function (item) { item.tabIndex = -1; }); } @@ -161,7 +164,6 @@ MenubarEditor.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { } newMenuitem.focus(); - }; MenubarEditor.prototype.setFocusToFirstMenuitem = function (menuId) { @@ -172,15 +174,17 @@ MenubarEditor.prototype.setFocusToLastMenuitem = function (menuId) { this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId]); }; -MenubarEditor.prototype.setFocusToPreviousMenuitem = function (menuId, currentMenuitem) { +MenubarEditor.prototype.setFocusToPreviousMenuitem = function ( + menuId, + currentMenuitem +) { var newMenuitem, index; if (currentMenuitem === this.firstMenuitem[menuId]) { newMenuitem = this.lastMenuitem[menuId]; - } - else { + } else { index = this.menuitemGroups[menuId].indexOf(currentMenuitem); - newMenuitem = this.menuitemGroups[menuId][ index - 1 ]; + newMenuitem = this.menuitemGroups[menuId][index - 1]; } this.setFocusToMenuitem(menuId, newMenuitem); @@ -188,29 +192,35 @@ MenubarEditor.prototype.setFocusToPreviousMenuitem = function (menuId, currentMe return newMenuitem; }; -MenubarEditor.prototype.setFocusToNextMenuitem = function (menuId, currentMenuitem) { +MenubarEditor.prototype.setFocusToNextMenuitem = function ( + menuId, + currentMenuitem +) { var newMenuitem, index; if (currentMenuitem === this.lastMenuitem[menuId]) { newMenuitem = this.firstMenuitem[menuId]; - } - else { + } else { index = this.menuitemGroups[menuId].indexOf(currentMenuitem); - newMenuitem = this.menuitemGroups[menuId][ index + 1 ]; + newMenuitem = this.menuitemGroups[menuId][index + 1]; } this.setFocusToMenuitem(menuId, newMenuitem); return newMenuitem; }; -MenubarEditor.prototype.setFocusByFirstCharacter = function (menuId, currentMenuitem, char) { +MenubarEditor.prototype.setFocusByFirstCharacter = function ( + menuId, + currentMenuitem, + char +) { var start, index; char = char.toLowerCase(); // Get start index for search based on position of currentItem start = this.menuitemGroups[menuId].indexOf(currentMenuitem) + 1; - if (start >= this.menuitemGroups[menuId].length) { + if (start >= this.menuitemGroups[menuId].length) { start = 0; } @@ -230,7 +240,11 @@ MenubarEditor.prototype.setFocusByFirstCharacter = function (menuId, currentMenu // Utilities -MenubarEditor.prototype.getIndexFirstChars = function (menuId, startIndex, char) { +MenubarEditor.prototype.getIndexFirstChars = function ( + menuId, + startIndex, + char +) { for (var i = startIndex; i < this.firstChars[menuId].length; i++) { if (char === this.firstChars[menuId][i]) { return i; @@ -239,21 +253,19 @@ MenubarEditor.prototype.getIndexFirstChars = function (menuId, startIndex, char) return -1; }; -MenubarEditor.prototype.isPrintableCharacter = function(str) { - return str.length === 1 && str.match(/\S/); +MenubarEditor.prototype.isPrintableCharacter = function (str) { + return str.length === 1 && str.match(/\S/); }; -MenubarEditor.prototype.getIdFromAriaLabel = function(node) { - var id = node.getAttribute('aria-label') +MenubarEditor.prototype.getIdFromAriaLabel = function (node) { + var id = node.getAttribute('aria-label'); if (id) { id = id.trim().toLowerCase().replace(' ', '-').replace('/', '-'); } return id; }; - -MenubarEditor.prototype.getMenuOrientation = function(node) { - +MenubarEditor.prototype.getMenuOrientation = function (node) { var orientation = node.getAttribute('aria-orientation'); if (!orientation) { @@ -276,17 +288,13 @@ MenubarEditor.prototype.getMenuOrientation = function(node) { return orientation; }; -MenubarEditor.prototype.getDataOption = function(node) { - +MenubarEditor.prototype.getDataOption = function (node) { var option = false; var hasOption = node.hasAttribute('data-option'); var role = node.hasAttribute('role'); if (!hasOption) { - - while (node && !hasOption && - (role !== 'menu') && - (role !== 'menubar')) { + while (node && !hasOption && role !== 'menu' && role !== 'menubar') { node = node.parentNode; if (node) { role = node.getAttribute('role'); @@ -302,19 +310,16 @@ MenubarEditor.prototype.getDataOption = function(node) { return option; }; -MenubarEditor.prototype.getGroupId = function(node) { - +MenubarEditor.prototype.getGroupId = function (node) { var id = false; var role = node.getAttribute('role'); - while (node && (role !== 'group') && - (role !== 'menu') && - (role !== 'menubar')) { + while (node && role !== 'group' && role !== 'menu' && role !== 'menubar') { node = node.parentNode; if (node) { role = node.getAttribute('role'); } - } + } if (node) { id = role + '-' + this.getIdFromAriaLabel(node); @@ -323,12 +328,11 @@ MenubarEditor.prototype.getGroupId = function(node) { return id; }; -MenubarEditor.prototype.getMenuId = function(node) { - +MenubarEditor.prototype.getMenuId = function (node) { var id = false; var role = node.getAttribute('role'); - while (node && (role !== 'menu') && (role !== 'menubar')) { + while (node && role !== 'menu' && role !== 'menubar') { node = node.parentNode; if (node) { role = node.getAttribute('role'); @@ -342,14 +346,13 @@ MenubarEditor.prototype.getMenuId = function(node) { return id; }; -MenubarEditor.prototype.getMenu = function(menuitem) { - +MenubarEditor.prototype.getMenu = function (menuitem) { var id = false; var menu = menuitem; var role = menuitem.getAttribute('role'); - while (menu && (role !== 'menu') && (role !== 'menubar')) { - menu = menu.parentNode + while (menu && role !== 'menu' && role !== 'menubar') { + menu = menu.parentNode; if (menu) { role = menu.getAttribute('role'); } @@ -358,7 +361,7 @@ MenubarEditor.prototype.getMenu = function(menuitem) { return menu; }; -MenubarEditor.prototype.toggleCheckbox = function(menuitem) { +MenubarEditor.prototype.toggleCheckbox = function (menuitem) { if (menuitem.getAttribute('aria-checked') === 'true') { menuitem.setAttribute('aria-checked', 'false'); return false; @@ -367,18 +370,17 @@ MenubarEditor.prototype.toggleCheckbox = function(menuitem) { return true; }; -MenubarEditor.prototype.setRadioButton = function(menuitem) { +MenubarEditor.prototype.setRadioButton = function (menuitem) { var groupId = this.getGroupId(menuitem); var radiogroupItems = this.menuitemGroups[groupId]; - radiogroupItems.forEach( function (item) { - item.setAttribute('aria-checked', 'false') + radiogroupItems.forEach(function (item) { + item.setAttribute('aria-checked', 'false'); }); menuitem.setAttribute('aria-checked', 'true'); return menuitem.textContent; }; -MenubarEditor.prototype.updateFontSizeMenu = function(menuId) { - +MenubarEditor.prototype.updateFontSizeMenu = function (menuId) { var fontSizeMenuitems = this.menuitemGroups[menuId]; var currentValue = this.actionManager.getFontSize(); @@ -391,8 +393,7 @@ MenubarEditor.prototype.updateFontSizeMenu = function(menuId) { case 'font-smaller': if (currentValue === 'x-small') { mi.setAttribute('aria-disabled', 'true'); - } - else { + } else { mi.removeAttribute('aria-disabled'); } break; @@ -400,8 +401,7 @@ MenubarEditor.prototype.updateFontSizeMenu = function(menuId) { case 'font-larger': if (currentValue === 'x-large') { mi.setAttribute('aria-disabled', 'true'); - } - else { + } else { mi.removeAttribute('aria-disabled'); } break; @@ -409,17 +409,13 @@ MenubarEditor.prototype.updateFontSizeMenu = function(menuId) { default: if (currentValue === value) { mi.setAttribute('aria-checked', 'true'); - } - else { + } else { mi.setAttribute('aria-checked', 'false'); } break; - } } - - -} +}; // Popup menu methods @@ -433,7 +429,6 @@ MenubarEditor.prototype.isAnyPopupOpen = function () { }; MenubarEditor.prototype.openPopup = function (menuitem) { - // set aria-expanded attribute var popupMenu = menuitem.nextElementSibling; @@ -441,7 +436,7 @@ MenubarEditor.prototype.openPopup = function (menuitem) { // set CSS properties popupMenu.style.position = 'absolute'; - popupMenu.style.top = (rect.height - 3) + 'px'; + popupMenu.style.top = rect.height - 3 + 'px'; popupMenu.style.left = '0px'; popupMenu.style.zIndex = 100; popupMenu.style.display = 'block'; @@ -449,7 +444,6 @@ MenubarEditor.prototype.openPopup = function (menuitem) { menuitem.setAttribute('aria-expanded', 'true'); return this.getMenuId(popupMenu); - }; MenubarEditor.prototype.closePopup = function (menuitem) { @@ -460,10 +454,8 @@ MenubarEditor.prototype.closePopup = function (menuitem) { menuitem.setAttribute('aria-expanded', 'false'); menuitem.nextElementSibling.style.display = 'none'; menuitem.nextElementSibling.style.zIndex = 0; - } - } - else { + } else { menu = this.getMenu(menuitem); cmi = menu.previousElementSibling; cmi.setAttribute('aria-expanded', 'false'); @@ -533,14 +525,13 @@ MenubarEditor.prototype.handleKeydown = function (event) { switch (key) { case ' ': case 'Enter': - if (this.hasPopup(tgt)) { + if (this.hasPopup(tgt)) { popupMenuId = this.openPopup(tgt); this.setFocusToFirstMenuitem(popupMenuId); - } - else { + } else { role = tgt.getAttribute('role'); option = this.getDataOption(tgt); - switch(role) { + switch (role) { case 'menuitem': this.actionManager.setOption(option, tgt.textContent); break; @@ -565,15 +556,14 @@ MenubarEditor.prototype.handleKeydown = function (event) { this.closePopup(tgt); } flag = true; - break; + break; case 'ArrowDown': case 'Down': if (this.menuOrientation[menuId] === 'vertical') { this.setFocusToNextMenuitem(menuId, tgt); flag = true; - } - else { + } else { if (this.hasPopup(tgt)) { popupMenuId = this.openPopup(tgt); this.setFocusToFirstMenuitem(popupMenuId); @@ -584,8 +574,8 @@ MenubarEditor.prototype.handleKeydown = function (event) { case 'Esc': case 'Escape': - this.closePopup(tgt); - flag = true; + this.closePopup(tgt); + flag = true; break; case 'Left': @@ -593,8 +583,7 @@ MenubarEditor.prototype.handleKeydown = function (event) { if (this.menuOrientation[menuId] === 'horizontal') { this.setFocusToPreviousMenuitem(menuId, tgt); flag = true; - } - else { + } else { mi = this.closePopup(tgt); id = this.getMenuId(mi); mi = this.setFocusToPreviousMenuitem(id, mi); @@ -607,8 +596,7 @@ MenubarEditor.prototype.handleKeydown = function (event) { if (this.menuOrientation[menuId] === 'horizontal') { this.setFocusToNextMenuitem(menuId, tgt); flag = true; - } - else { + } else { mi = this.closePopup(tgt); id = this.getMenuId(mi); mi = this.setFocusToNextMenuitem(id, mi); @@ -621,8 +609,7 @@ MenubarEditor.prototype.handleKeydown = function (event) { if (this.menuOrientation[menuId] === 'vertical') { this.setFocusToPreviousMenuitem(menuId, tgt); flag = true; - } - else { + } else { if (this.hasPopup(tgt)) { popupMenuId = this.openPopup(tgt); this.setFocusToLastMenuitem(popupMenuId); @@ -668,16 +655,14 @@ MenubarEditor.prototype.handleMenuitemClick = function (event) { if (this.hasPopup(tgt)) { if (this.isOpen(tgt)) { this.closePopup(tgt); - } - else { + } else { var menuId = this.openPopup(tgt); this.setFocusToMenuitem(menuId, tgt); } - } - else { + } else { var role = tgt.getAttribute('role'); var option = this.getDataOption(tgt); - switch(role) { + switch (role) { case 'menuitem': this.actionManager.setOption(option, tgt.textContent); break; @@ -704,7 +689,6 @@ MenubarEditor.prototype.handleMenuitemClick = function (event) { event.stopPropagation(); event.preventDefault(); - }; MenubarEditor.prototype.handleMenuitemMouseover = function (event) { @@ -719,7 +703,7 @@ MenubarEditor.prototype.handleMenuitemMouseover = function (event) { window.addEventListener('load', function () { var menubarEditors = document.querySelectorAll('.menubar-editor'); - for(var i=0; i < menubarEditors.length; i++) { + for (var i = 0; i < menubarEditors.length; i++) { var menubarEditor = new MenubarEditor(menubarEditors[i]); } }); diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 860943ec4f..af27ed0f38 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -1,619 +1,630 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: menubar-navigation.js -* -* Desc: Creates a menubar of hierarchical set of links -*/ - -'use strict'; - -var MenubarNavigation = function (domNode) { - - this.domNode = domNode; - - - this.popups = []; - this.menuitemGroups = {}; - this.menuOrientation = {}; - this.isPopup = {}; - this.isPopout = {}; - this.openPopups = false; - - this.firstChars = {}; // see Menubar init method - this.firstMenuitem = {}; // see Menubar init method - this.lastMenuitem = {}; // see Menubar init method - - this.initMenu(domNode, 0); - - domNode.addEventListener('focusin', this.handleMenubarFocusin.bind(this)); - domNode.addEventListener('focusout', this.handleMenubarFocusout.bind(this)); - - window.addEventListener('mousedown', this.handleBackgroundMousedown.bind(this), true); -}; - -MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { - var nodes = []; - - var initMenu = this.initMenu.bind(this); - var menuitemGroups = this.menuitemGroups; - var popups = this.popups; - - function findMenuitems(node) { - var role, flag; - - while (node) { - flag = true; - role = node.getAttribute('role'); - - if (role) { - role = role.trim().toLowerCase(); - } - - switch (role) { - case 'menu': - node.tabIndex = -1; - initMenu(node, depth + 1); - flag = false; - break; - - case 'menuitem': - if (node.getAttribute('aria-haspopup') === 'true') { - popups.push(node); - } - nodes.push(node); - break; - - default: - break; - } - - if (flag && node.firstElementChild && node.firstElementChild.tagName !== 'svg') { - findMenuitems(node.firstElementChild); - } - - node = node.nextElementSibling; - } - } - - findMenuitems(domNode.firstElementChild); - - return nodes; -}; - -MenubarNavigation.prototype.initMenu = function (menu, depth) { - var menuitems, menuitem, role, nextElement; - - var menuId = this.getMenuId(menu); - - menuitems = this.getMenuitems(menu, depth); - this.menuOrientation[menuId] = this.getMenuOrientation(menu); - - this.isPopup[menuId] = (menu.getAttribute('role') === 'menu') && (depth === 1); - this.isPopout[menuId] = (menu.getAttribute('role') === 'menu') && (depth > 1); - - this.menuitemGroups[menuId] = []; - this.firstChars[menuId] = []; - this.firstMenuitem[menuId] = null; - this.lastMenuitem[menuId] = null; - - for(var i = 0; i < menuitems.length; i++) { - menuitem = menuitems[i]; - role = menuitem.getAttribute('role'); - - if (role.indexOf('menuitem') < 0) { - continue; - } - - menuitem.tabIndex = -1; - this.menuitemGroups[menuId].push(menuitem); - this.firstChars[menuId].push(menuitem.textContent.trim().toLowerCase()[0]); - - menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); - menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); - - menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); - - if( !this.firstMenuitem[menuId]) { - if (this.hasPopup(menuitem)) { - menuitem.tabIndex = 0; - } - this.firstMenuitem[menuId] = menuitem; - } - this.lastMenuitem[menuId] = menuitem; - - } -}; - -MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { - - this.closePopupAll(newMenuitem); - - if (this.menuitemGroups[menuId]) { - this.menuitemGroups[menuId].forEach(function(item) { - if (item === newMenuitem) { - item.tabIndex = 0; - newMenuitem.focus(); - } - else { - item.tabIndex = -1; - } - }); - } -}; - -MenubarNavigation.prototype.setFocusToFirstMenuitem = function (menuId, currentMenuitem) { - this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId]); -}; - -MenubarNavigation.prototype.setFocusToLastMenuitem = function (menuId, currentMenuitem) { - this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId]); -}; - -MenubarNavigation.prototype.setFocusToPreviousMenuitem = function (menuId, currentMenuitem) { - var newMenuitem, index; - - if (currentMenuitem === this.firstMenuitem[menuId]) { - newMenuitem = this.lastMenuitem[menuId]; - } - else { - index = this.menuitemGroups[menuId].indexOf(currentMenuitem); - newMenuitem = this.menuitemGroups[menuId][ index - 1 ]; - } - - this.setFocusToMenuitem(menuId, newMenuitem); - - return newMenuitem; -}; - -MenubarNavigation.prototype.setFocusToNextMenuitem = function (menuId, currentMenuitem) { - var newMenuitem, index; - - if (currentMenuitem === this.lastMenuitem[menuId]) { - newMenuitem = this.firstMenuitem[menuId]; - } - else { - index = this.menuitemGroups[menuId].indexOf(currentMenuitem); - newMenuitem = this.menuitemGroups[menuId][ index + 1 ]; - } - this.setFocusToMenuitem(menuId, newMenuitem); - - return newMenuitem; -}; - -MenubarNavigation.prototype.setFocusByFirstCharacter = function (menuId, currentMenuitem, char) { - var start, index; - - char = char.toLowerCase(); - - // Get start index for search based on position of currentItem - start = this.menuitemGroups[menuId].indexOf(currentMenuitem) + 1; - if (start >= this.menuitemGroups[menuId].length) { - start = 0; - } - - // Check remaining slots in the menu - index = this.getIndexFirstChars(menuId, start, char); - - // If not found in remaining slots, check from beginning - if (index === -1) { - index = this.getIndexFirstChars(menuId, 0, char); - } - - // If match was found... - if (index > -1) { - this.setFocusToMenuitem(menuId, this.menuitemGroups[menuId][index]); - } -}; - -// Utitlities - -MenubarNavigation.prototype.getIndexFirstChars = function (menuId, startIndex, char) { - for (var i = startIndex; i < this.firstChars[menuId].length; i++) { - if (char === this.firstChars[menuId][i]) { - return i; - } - } - return -1; -}; - -MenubarNavigation.prototype.isPrintableCharacter = function(str) { - return str.length === 1 && str.match(/\S/); -}; - -MenubarNavigation.prototype.getIdFromAriaLabel = function(node) { - var id = node.getAttribute('aria-label') - if (id) { - id = id.trim().toLowerCase().replace(' ', '-').replace('/', '-'); - } - return id; -}; - - -MenubarNavigation.prototype.getMenuOrientation = function(node) { - - var orientation = node.getAttribute('aria-orientation'); - - if (!orientation) { - var role = node.getAttribute('role'); - - switch (role) { - case 'menubar': - orientation = 'horizontal'; - break; - - case 'menu': - orientation = 'vertical'; - break; - - default: - break; - } - } - - return orientation; -}; - -MenubarNavigation.prototype.getMenuId = function(node) { - - var id = false; - var role = node.getAttribute('role'); - - while (node && (role !== 'menu') && (role !== 'menubar')) { - node = node.parentNode; - if (node) { - role = node.getAttribute('role'); - } - } - - if (node) { - id = role + '-' + this.getIdFromAriaLabel(node); - } - - return id; -}; - -MenubarNavigation.prototype.getMenu = function(menuitem) { - - var id = false; - var menu = menuitem; - var role = menuitem.getAttribute('role'); - - while (menu && (role !== 'menu') && (role !== 'menubar')) { - menu = menu.parentNode - if (menu) { - role = menu.getAttribute('role'); - } - } - - return menu; -}; - -// Popup menu methods - -MenubarNavigation.prototype.isAnyPopupOpen = function () { - for (var i = 0; i < this.popups.length; i++) { - if (this.popups[i].getAttribute('aria-expanded') === 'true') { - return true; - } - } - return false; -}; - -MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { - - // set aria-expanded attribute - var popupMenu = menuitem.nextElementSibling; - - var rect = menuitem.getBoundingClientRect(); - - // Set CSS properties - if (this.isPopup[menuId]) { - popupMenu.parentNode.style.position = 'relative'; - popupMenu.style.display = 'block'; - popupMenu.style.position = 'absolute'; - popupMenu.style.left = (rect.width + 6) + 'px'; - popupMenu.style.top = '0px'; - popupMenu.style.zIndex = 100; - } - else { - popupMenu.style.display = 'block'; - popupMenu.style.position = 'absolute'; - popupMenu.style.left = '0px'; - popupMenu.style.top = (rect.height + 8)+ 'px'; - popupMenu.style.zIndex = 100; - } - - menuitem.setAttribute('aria-expanded', 'true'); - - return this.getMenuId(popupMenu); - -}; - -MenubarNavigation.prototype.closePopout = function (menuitem) { - var menu, - menuId = this.getMenuId(menuitem), - cmi = menuitem; - - while (this.isPopup[menuId] || this.isPopout[menuId]) { - menu = this.getMenu(cmi); - cmi = menu.previousElementSibling; - menuId = this.getMenuId(cmi); - cmi.setAttribute('aria-expanded', 'false'); - menu.style.display = 'none'; - } - cmi.focus(); - return cmi; -}; - -MenubarNavigation.prototype.closePopup = function (menuitem) { - var menu, - menuId = this.getMenuId(menuitem), - cmi = menuitem; - - if (this.isMenubar(menuId)) { - if (this.isOpen(menuitem)) { - menuitem.setAttribute('aria-expanded', 'false'); - menuitem.nextElementSibling.style.display = 'none'; - } - } - else { - menu = this.getMenu(menuitem); - cmi = menu.previousElementSibling; - cmi.setAttribute('aria-expanded', 'false'); - cmi.focus(); - menu.style.display = 'none'; - } - - return cmi; -}; - -MenubarNavigation.prototype.doesNotContain = function (popup, menuitem) { - if (menuitem) { - return !popup.nextElementSibling.contains(menuitem); - } - return true; -}; - -MenubarNavigation.prototype.closePopupAll = function (menuitem) { - if (typeof menuitem !== 'object') { - menuitem = false; - } - for (var i = 0; i < this.popups.length; i++) { - var popup = this.popups[i]; - if (this.doesNotContain(popup, menuitem) && this.isOpen(popup)) { - var cmi = popup.nextElementSibling; - if (cmi) { - popup.setAttribute('aria-expanded', 'false'); - cmi.style.display = 'none'; - } - } - } -}; - -MenubarNavigation.prototype.hasPopup = function (menuitem) { - return menuitem.getAttribute('aria-haspopup') === 'true'; -}; - -MenubarNavigation.prototype.isOpen = function (menuitem) { - return menuitem.getAttribute('aria-expanded') === 'true'; -}; - -MenubarNavigation.prototype.isMenubar = function (menuId) { - return !this.isPopup[menuId] && !this.isPopout[menuId]; -}; - -MenubarNavigation.prototype.isMenuHorizontal = function (menuitem) { - return this.menuOrientation[menuitem] === 'horizontal'; -}; - -MenubarNavigation.prototype.hasFocus = function () { - return this.domNode.classList.contains('focus'); -}; - -// Menu event handlers - -MenubarNavigation.prototype.handleMenubarFocusin = function (event) { - // if the menubar or any of its menus has focus, add styling hook for hover - this.domNode.classList.add('focus'); -}; - -MenubarNavigation.prototype.handleMenubarFocusout = function (event) { - // remove styling hook for hover on menubar item - this.domNode.classList.remove('focus'); -}; - -MenubarNavigation.prototype.handleKeydown = function (event) { - var tgt = event.currentTarget, - key = event.key, - flag = false, - menuId = this.getMenuId(tgt), - id, - popupMenuId, - mi, - role, - option, - value; - - var isAnyPopupOpen = this.isAnyPopupOpen(); - - switch (key) { - case ' ': - case 'Enter': - if (this.hasPopup(tgt)) { - this.openPopups = true; - popupMenuId = this.openPopup(menuId, tgt); - this.setFocusToFirstMenuitem(popupMenuId); - } - else { - if (tgt.href !== '#') { - this.closePopupAll(); - window.location.href=tgt.href; - } - } - flag = true; - break; - - case 'Esc': - case 'Escape': - this.openPopups = false; - this.closePopup(tgt); - flag = true; - break; - - case 'Up': - case 'ArrowUp': - if (this.isMenuHorizontal(menuId)) { - if (this.hasPopup(tgt)) { - this.openPopups = true; - popupMenuId = this.openPopup(menuId, tgt); - this.setFocusToLastMenuitem(popupMenuId); - } - } - else { - this.setFocusToPreviousMenuitem(menuId, tgt); - } - flag = true; - break; - - case 'ArrowDown': - case 'Down': - if (this.isMenuHorizontal(menuId)) { - if (this.hasPopup(tgt)) { - this.openPopups = true; - popupMenuId = this.openPopup(menuId, tgt); - this.setFocusToFirstMenuitem(popupMenuId); - } - } - else { - this.setFocusToNextMenuitem(menuId, tgt); - } - flag = true; - break; - - case 'Left': - case 'ArrowLeft': - if (this.isMenuHorizontal(menuId)) { - mi = this.setFocusToPreviousMenuitem(menuId, tgt); - if (isAnyPopupOpen) { - this.openPopup(menuId, mi); - } - } - else { - if (this.isPopout[menuId]) { - mi = this.closePopup(tgt); - id = this.getMenuId(mi); - mi = this.setFocusToMenuitem(id, mi); - } - else { - mi = this.closePopup(tgt); - id = this.getMenuId(mi); - mi = this.setFocusToPreviousMenuitem(id, mi); - this.openPopup(id, mi); - } - } - flag = true; - break; - - case 'Right': - case 'ArrowRight': - if (this.isMenuHorizontal(menuId)) { - mi = this.setFocusToNextMenuitem(menuId, tgt); - if (isAnyPopupOpen) { - this.openPopup(menuId, mi); - } - } - else { - if (this.hasPopup(tgt)) { - popupMenuId = this.openPopup(menuId, tgt); - this.setFocusToFirstMenuitem(popupMenuId); - } - else { - mi = this.closePopout(tgt); - id = this.getMenuId(mi); - mi = this.setFocusToNextMenuitem(id, mi); - this.openPopup(id, mi); - } - } - flag = true; - break; - - case 'Home': - case 'PageUp': - this.setFocusToFirstMenuitem(menuId, tgt); - flag = true; - break; - - case 'End': - case 'PageDown': - this.setFocusToLastMenuitem(menuId, tgt); - flag = true; - break; - - case 'Tab': - this.openPopups = false; - this.closePopup(tgt); - break; - - default: - if (this.isPrintableCharacter(key)) { - this.setFocusByFirstCharacter(menuId, tgt, key); - flag = true; - } - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -MenubarNavigation.prototype.handleMenuitemClick = function (event) { - var tgt = event.currentTarget; - var menuId = this.getMenuId(tgt); - - if (this.hasPopup(tgt)) { - if (this.isOpen(tgt)) { - this.closePopup(tgt); - } - else { - this.closePopupAll(tgt); - this.openPopup(menuId, tgt); - } - event.stopPropagation(); - event.preventDefault(); - } -}; - -MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { - var tgt = event.currentTarget; - var menuId = this.getMenuId(tgt); - - if (this.hasFocus()) { - this.setFocusToMenuitem(menuId, tgt); - } - - if (this.isAnyPopupOpen() || this.hasFocus()) { - this.closePopupAll(tgt); - if (this.hasPopup(tgt)) { - this.openPopup(menuId, tgt); - } - } -}; - -MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { - if (!this.domNode.contains(event.target)) { - this.closePopupAll(); - } -}; - -// Initialize menubar editor - -window.addEventListener('load', function () { - var menubarNavs = document.querySelectorAll('.menubar-navigation'); - for(var i=0; i < menubarNavs.length; i++) { - var menubarNav = new MenubarNavigation(menubarNavs[i]); - } -}); +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: menubar-navigation.js + * + * Desc: Creates a menubar of hierarchical set of links + */ + +'use strict'; + +var MenubarNavigation = function (domNode) { + this.domNode = domNode; + + this.popups = []; + this.menuitemGroups = {}; + this.menuOrientation = {}; + this.isPopup = {}; + this.isPopout = {}; + this.openPopups = false; + + this.firstChars = {}; // see Menubar init method + this.firstMenuitem = {}; // see Menubar init method + this.lastMenuitem = {}; // see Menubar init method + + this.initMenu(domNode, 0); + + domNode.addEventListener('focusin', this.handleMenubarFocusin.bind(this)); + domNode.addEventListener('focusout', this.handleMenubarFocusout.bind(this)); + + window.addEventListener( + 'mousedown', + this.handleBackgroundMousedown.bind(this), + true + ); +}; + +MenubarNavigation.prototype.getMenuitems = function (domNode, depth) { + var nodes = []; + + var initMenu = this.initMenu.bind(this); + var menuitemGroups = this.menuitemGroups; + var popups = this.popups; + + function findMenuitems(node) { + var role, flag; + + while (node) { + flag = true; + role = node.getAttribute('role'); + + if (role) { + role = role.trim().toLowerCase(); + } + + switch (role) { + case 'menu': + node.tabIndex = -1; + initMenu(node, depth + 1); + flag = false; + break; + + case 'menuitem': + if (node.getAttribute('aria-haspopup') === 'true') { + popups.push(node); + } + nodes.push(node); + break; + + default: + break; + } + + if ( + flag && + node.firstElementChild && + node.firstElementChild.tagName !== 'svg' + ) { + findMenuitems(node.firstElementChild); + } + + node = node.nextElementSibling; + } + } + + findMenuitems(domNode.firstElementChild); + + return nodes; +}; + +MenubarNavigation.prototype.initMenu = function (menu, depth) { + var menuitems, menuitem, role, nextElement; + + var menuId = this.getMenuId(menu); + + menuitems = this.getMenuitems(menu, depth); + this.menuOrientation[menuId] = this.getMenuOrientation(menu); + + this.isPopup[menuId] = menu.getAttribute('role') === 'menu' && depth === 1; + this.isPopout[menuId] = menu.getAttribute('role') === 'menu' && depth > 1; + + this.menuitemGroups[menuId] = []; + this.firstChars[menuId] = []; + this.firstMenuitem[menuId] = null; + this.lastMenuitem[menuId] = null; + + for (var i = 0; i < menuitems.length; i++) { + menuitem = menuitems[i]; + role = menuitem.getAttribute('role'); + + if (role.indexOf('menuitem') < 0) { + continue; + } + + menuitem.tabIndex = -1; + this.menuitemGroups[menuId].push(menuitem); + this.firstChars[menuId].push(menuitem.textContent.trim().toLowerCase()[0]); + + menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); + menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); + + menuitem.addEventListener( + 'mouseover', + this.handleMenuitemMouseover.bind(this) + ); + + if (!this.firstMenuitem[menuId]) { + if (this.hasPopup(menuitem)) { + menuitem.tabIndex = 0; + } + this.firstMenuitem[menuId] = menuitem; + } + this.lastMenuitem[menuId] = menuitem; + } +}; + +MenubarNavigation.prototype.setFocusToMenuitem = function ( + menuId, + newMenuitem +) { + this.closePopupAll(newMenuitem); + + if (this.menuitemGroups[menuId]) { + this.menuitemGroups[menuId].forEach(function (item) { + if (item === newMenuitem) { + item.tabIndex = 0; + newMenuitem.focus(); + } else { + item.tabIndex = -1; + } + }); + } +}; + +MenubarNavigation.prototype.setFocusToFirstMenuitem = function ( + menuId, + currentMenuitem +) { + this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId]); +}; + +MenubarNavigation.prototype.setFocusToLastMenuitem = function ( + menuId, + currentMenuitem +) { + this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId]); +}; + +MenubarNavigation.prototype.setFocusToPreviousMenuitem = function ( + menuId, + currentMenuitem +) { + var newMenuitem, index; + + if (currentMenuitem === this.firstMenuitem[menuId]) { + newMenuitem = this.lastMenuitem[menuId]; + } else { + index = this.menuitemGroups[menuId].indexOf(currentMenuitem); + newMenuitem = this.menuitemGroups[menuId][index - 1]; + } + + this.setFocusToMenuitem(menuId, newMenuitem); + + return newMenuitem; +}; + +MenubarNavigation.prototype.setFocusToNextMenuitem = function ( + menuId, + currentMenuitem +) { + var newMenuitem, index; + + if (currentMenuitem === this.lastMenuitem[menuId]) { + newMenuitem = this.firstMenuitem[menuId]; + } else { + index = this.menuitemGroups[menuId].indexOf(currentMenuitem); + newMenuitem = this.menuitemGroups[menuId][index + 1]; + } + this.setFocusToMenuitem(menuId, newMenuitem); + + return newMenuitem; +}; + +MenubarNavigation.prototype.setFocusByFirstCharacter = function ( + menuId, + currentMenuitem, + char +) { + var start, index; + + char = char.toLowerCase(); + + // Get start index for search based on position of currentItem + start = this.menuitemGroups[menuId].indexOf(currentMenuitem) + 1; + if (start >= this.menuitemGroups[menuId].length) { + start = 0; + } + + // Check remaining slots in the menu + index = this.getIndexFirstChars(menuId, start, char); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = this.getIndexFirstChars(menuId, 0, char); + } + + // If match was found... + if (index > -1) { + this.setFocusToMenuitem(menuId, this.menuitemGroups[menuId][index]); + } +}; + +// Utitlities + +MenubarNavigation.prototype.getIndexFirstChars = function ( + menuId, + startIndex, + char +) { + for (var i = startIndex; i < this.firstChars[menuId].length; i++) { + if (char === this.firstChars[menuId][i]) { + return i; + } + } + return -1; +}; + +MenubarNavigation.prototype.isPrintableCharacter = function (str) { + return str.length === 1 && str.match(/\S/); +}; + +MenubarNavigation.prototype.getIdFromAriaLabel = function (node) { + var id = node.getAttribute('aria-label'); + if (id) { + id = id.trim().toLowerCase().replace(' ', '-').replace('/', '-'); + } + return id; +}; + +MenubarNavigation.prototype.getMenuOrientation = function (node) { + var orientation = node.getAttribute('aria-orientation'); + + if (!orientation) { + var role = node.getAttribute('role'); + + switch (role) { + case 'menubar': + orientation = 'horizontal'; + break; + + case 'menu': + orientation = 'vertical'; + break; + + default: + break; + } + } + + return orientation; +}; + +MenubarNavigation.prototype.getMenuId = function (node) { + var id = false; + var role = node.getAttribute('role'); + + while (node && role !== 'menu' && role !== 'menubar') { + node = node.parentNode; + if (node) { + role = node.getAttribute('role'); + } + } + + if (node) { + id = role + '-' + this.getIdFromAriaLabel(node); + } + + return id; +}; + +MenubarNavigation.prototype.getMenu = function (menuitem) { + var id = false; + var menu = menuitem; + var role = menuitem.getAttribute('role'); + + while (menu && role !== 'menu' && role !== 'menubar') { + menu = menu.parentNode; + if (menu) { + role = menu.getAttribute('role'); + } + } + + return menu; +}; + +// Popup menu methods + +MenubarNavigation.prototype.isAnyPopupOpen = function () { + for (var i = 0; i < this.popups.length; i++) { + if (this.popups[i].getAttribute('aria-expanded') === 'true') { + return true; + } + } + return false; +}; + +MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { + // set aria-expanded attribute + var popupMenu = menuitem.nextElementSibling; + + var rect = menuitem.getBoundingClientRect(); + + // Set CSS properties + if (this.isPopup[menuId]) { + popupMenu.parentNode.style.position = 'relative'; + popupMenu.style.display = 'block'; + popupMenu.style.position = 'absolute'; + popupMenu.style.left = rect.width + 6 + 'px'; + popupMenu.style.top = '0px'; + popupMenu.style.zIndex = 100; + } else { + popupMenu.style.display = 'block'; + popupMenu.style.position = 'absolute'; + popupMenu.style.left = '0px'; + popupMenu.style.top = rect.height + 8 + 'px'; + popupMenu.style.zIndex = 100; + } + + menuitem.setAttribute('aria-expanded', 'true'); + + return this.getMenuId(popupMenu); +}; + +MenubarNavigation.prototype.closePopout = function (menuitem) { + var menu, + menuId = this.getMenuId(menuitem), + cmi = menuitem; + + while (this.isPopup[menuId] || this.isPopout[menuId]) { + menu = this.getMenu(cmi); + cmi = menu.previousElementSibling; + menuId = this.getMenuId(cmi); + cmi.setAttribute('aria-expanded', 'false'); + menu.style.display = 'none'; + } + cmi.focus(); + return cmi; +}; + +MenubarNavigation.prototype.closePopup = function (menuitem) { + var menu, + menuId = this.getMenuId(menuitem), + cmi = menuitem; + + if (this.isMenubar(menuId)) { + if (this.isOpen(menuitem)) { + menuitem.setAttribute('aria-expanded', 'false'); + menuitem.nextElementSibling.style.display = 'none'; + } + } else { + menu = this.getMenu(menuitem); + cmi = menu.previousElementSibling; + cmi.setAttribute('aria-expanded', 'false'); + cmi.focus(); + menu.style.display = 'none'; + } + + return cmi; +}; + +MenubarNavigation.prototype.doesNotContain = function (popup, menuitem) { + if (menuitem) { + return !popup.nextElementSibling.contains(menuitem); + } + return true; +}; + +MenubarNavigation.prototype.closePopupAll = function (menuitem) { + if (typeof menuitem !== 'object') { + menuitem = false; + } + for (var i = 0; i < this.popups.length; i++) { + var popup = this.popups[i]; + if (this.doesNotContain(popup, menuitem) && this.isOpen(popup)) { + var cmi = popup.nextElementSibling; + if (cmi) { + popup.setAttribute('aria-expanded', 'false'); + cmi.style.display = 'none'; + } + } + } +}; + +MenubarNavigation.prototype.hasPopup = function (menuitem) { + return menuitem.getAttribute('aria-haspopup') === 'true'; +}; + +MenubarNavigation.prototype.isOpen = function (menuitem) { + return menuitem.getAttribute('aria-expanded') === 'true'; +}; + +MenubarNavigation.prototype.isMenubar = function (menuId) { + return !this.isPopup[menuId] && !this.isPopout[menuId]; +}; + +MenubarNavigation.prototype.isMenuHorizontal = function (menuitem) { + return this.menuOrientation[menuitem] === 'horizontal'; +}; + +MenubarNavigation.prototype.hasFocus = function () { + return this.domNode.classList.contains('focus'); +}; + +// Menu event handlers + +MenubarNavigation.prototype.handleMenubarFocusin = function (event) { + // if the menubar or any of its menus has focus, add styling hook for hover + this.domNode.classList.add('focus'); +}; + +MenubarNavigation.prototype.handleMenubarFocusout = function (event) { + // remove styling hook for hover on menubar item + this.domNode.classList.remove('focus'); +}; + +MenubarNavigation.prototype.handleKeydown = function (event) { + var tgt = event.currentTarget, + key = event.key, + flag = false, + menuId = this.getMenuId(tgt), + id, + popupMenuId, + mi, + role, + option, + value; + + var isAnyPopupOpen = this.isAnyPopupOpen(); + + switch (key) { + case ' ': + case 'Enter': + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } else { + if (tgt.href !== '#') { + this.closePopupAll(); + window.location.href = tgt.href; + } + } + flag = true; + break; + + case 'Esc': + case 'Escape': + this.openPopups = false; + this.closePopup(tgt); + flag = true; + break; + + case 'Up': + case 'ArrowUp': + if (this.isMenuHorizontal(menuId)) { + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToLastMenuitem(popupMenuId); + } + } else { + this.setFocusToPreviousMenuitem(menuId, tgt); + } + flag = true; + break; + + case 'ArrowDown': + case 'Down': + if (this.isMenuHorizontal(menuId)) { + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } + } else { + this.setFocusToNextMenuitem(menuId, tgt); + } + flag = true; + break; + + case 'Left': + case 'ArrowLeft': + if (this.isMenuHorizontal(menuId)) { + mi = this.setFocusToPreviousMenuitem(menuId, tgt); + if (isAnyPopupOpen) { + this.openPopup(menuId, mi); + } + } else { + if (this.isPopout[menuId]) { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToMenuitem(id, mi); + } else { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToPreviousMenuitem(id, mi); + this.openPopup(id, mi); + } + } + flag = true; + break; + + case 'Right': + case 'ArrowRight': + if (this.isMenuHorizontal(menuId)) { + mi = this.setFocusToNextMenuitem(menuId, tgt); + if (isAnyPopupOpen) { + this.openPopup(menuId, mi); + } + } else { + if (this.hasPopup(tgt)) { + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } else { + mi = this.closePopout(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToNextMenuitem(id, mi); + this.openPopup(id, mi); + } + } + flag = true; + break; + + case 'Home': + case 'PageUp': + this.setFocusToFirstMenuitem(menuId, tgt); + flag = true; + break; + + case 'End': + case 'PageDown': + this.setFocusToLastMenuitem(menuId, tgt); + flag = true; + break; + + case 'Tab': + this.openPopups = false; + this.closePopup(tgt); + break; + + default: + if (this.isPrintableCharacter(key)) { + this.setFocusByFirstCharacter(menuId, tgt, key); + flag = true; + } + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenubarNavigation.prototype.handleMenuitemClick = function (event) { + var tgt = event.currentTarget; + var menuId = this.getMenuId(tgt); + + if (this.hasPopup(tgt)) { + if (this.isOpen(tgt)) { + this.closePopup(tgt); + } else { + this.closePopupAll(tgt); + this.openPopup(menuId, tgt); + } + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { + var tgt = event.currentTarget; + var menuId = this.getMenuId(tgt); + + if (this.hasFocus()) { + this.setFocusToMenuitem(menuId, tgt); + } + + if (this.isAnyPopupOpen() || this.hasFocus()) { + this.closePopupAll(tgt); + if (this.hasPopup(tgt)) { + this.openPopup(menuId, tgt); + } + } +}; + +MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { + if (!this.domNode.contains(event.target)) { + this.closePopupAll(); + } +}; + +// Initialize menubar editor + +window.addEventListener('load', function () { + var menubarNavs = document.querySelectorAll('.menubar-navigation'); + for (var i = 0; i < menubarNavs.length; i++) { + var menubarNav = new MenubarNavigation(menubarNavs[i]); + } +}); diff --git a/examples/menubar/js/style-manager.js b/examples/menubar/js/style-manager.js index 36f6264a09..06e35d7c4b 100644 --- a/examples/menubar/js/style-manager.js +++ b/examples/menubar/js/style-manager.js @@ -1,11 +1,11 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: TextStyling.js -* -* Desc: Styling functions for changing the style of an item -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: TextStyling.js + * + * Desc: Styling functions for changing the style of an item + */ 'use strict'; @@ -36,27 +36,22 @@ StyleManager.prototype.setColor = function (value) { }; StyleManager.prototype.setBold = function (flag) { - if (flag) { this.node.style.fontWeight = 'bold'; - } - else { + } else { this.node.style.fontWeight = 'normal'; } }; StyleManager.prototype.setItalic = function (flag) { - if (flag) { this.node.style.fontStyle = 'italic'; - } - else { + } else { this.node.style.fontStyle = 'normal'; } }; StyleManager.prototype.fontSmaller = function () { - switch (this.fontSize) { case 'small': this.setFontSize('x-small'); @@ -76,12 +71,10 @@ StyleManager.prototype.fontSmaller = function () { default: break; - } // end switch }; StyleManager.prototype.fontLarger = function () { - switch (this.fontSize) { case 'x-small': this.setFontSize('small'); @@ -101,7 +94,6 @@ StyleManager.prototype.fontLarger = function () { default: break; - } // end switch }; @@ -118,14 +110,12 @@ StyleManager.prototype.getFontSize = function () { }; StyleManager.prototype.setOption = function (option, value) { - option = option.toLowerCase(); if (typeof value === 'string') { - value = value.toLowerCase(); + value = value.toLowerCase(); } switch (option) { - case 'font-bold': this.setBold(value); break; @@ -164,7 +154,5 @@ StyleManager.prototype.setOption = function (option, value) { default: break; - } // end switch - }; diff --git a/examples/meter/js/meter.js b/examples/meter/js/meter.js index a046ae8b3a..1eb084dfc1 100644 --- a/examples/meter/js/meter.js +++ b/examples/meter/js/meter.js @@ -14,11 +14,15 @@ var Meter = function (element) { // returns a number representing a percentage between 0 - 100 Meter.prototype._calculatePercentFill = function (min, max, value) { - if (typeof min !== 'number' || typeof max !== 'number' || typeof value !== 'number') { + if ( + typeof min !== 'number' || + typeof max !== 'number' || + typeof value !== 'number' + ) { return 0; } - return 100 * (value - min) / (max - min); + return (100 * (value - min)) / (max - min); }; // returns an hsl color string between red and green @@ -73,9 +77,9 @@ Meter.prototype.setValue = function (value) { window.addEventListener('load', function () { // helper function to randomize example meter value - function getRandomValue (min, max) { + function getRandomValue(min, max) { var range = max - min; - return Math.floor((Math.random() * range) + min); + return Math.floor(Math.random() * range + min); } // init meters @@ -88,7 +92,7 @@ window.addEventListener('load', function () { // randomly update meter values // returns an id for setInterval - function playMeters () { + function playMeters() { return window.setInterval(function () { meters.forEach(function (meter) { meter.setValue(Math.random() * 100); @@ -108,8 +112,7 @@ window.addEventListener('load', function () { updateInterval = playMeters(); playButton.classList.remove('paused'); playButton.innerHTML = 'Pause Updates'; - } - else { + } else { clearInterval(updateInterval); playButton.classList.add('paused'); playButton.innerHTML = 'Start Updates'; diff --git a/examples/radio/js/radio-activedescendant.js b/examples/radio/js/radio-activedescendant.js index 6b5ebd5d00..0ba8c997c9 100644 --- a/examples/radio/js/radio-activedescendant.js +++ b/examples/radio/js/radio-activedescendant.js @@ -1,22 +1,21 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: radio-activedescendant.js -* -* Desc: Radio group widget using aria-activedescendant that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: radio-activedescendant.js + * + * Desc: Radio group widget using aria-activedescendant that implements ARIA Authoring Practices + */ 'use strict'; var RadioGroupActiveDescendant = function (groupNode) { - this.groupNode = groupNode; this.radioButtons = []; - this.firstRadioButton = null; - this.lastRadioButton = null; + this.firstRadioButton = null; + this.lastRadioButton = null; this.groupNode.addEventListener('keydown', this.handleKeydown.bind(this)); this.groupNode.addEventListener('focus', this.handleFocus.bind(this)); @@ -41,7 +40,7 @@ var RadioGroupActiveDescendant = function (groupNode) { this.groupNode.tabIndex = 0; }; -RadioGroupActiveDescendant.prototype.setChecked = function (currentItem) { +RadioGroupActiveDescendant.prototype.setChecked = function (currentItem) { for (var i = 0; i < this.radioButtons.length; i++) { var rb = this.radioButtons[i]; rb.setAttribute('aria-checked', 'false'); @@ -53,25 +52,27 @@ RadioGroupActiveDescendant.prototype.setChecked = function (currentItem) { this.groupNode.focus(); }; -RadioGroupActiveDescendant.prototype.setCheckedToPreviousItem = function (currentItem) { +RadioGroupActiveDescendant.prototype.setCheckedToPreviousItem = function ( + currentItem +) { var index; if (currentItem === this.firstRadioButton) { this.setChecked(this.lastRadioButton); - } - else { + } else { index = this.radioButtons.indexOf(currentItem); this.setChecked(this.radioButtons[index - 1]); } }; -RadioGroupActiveDescendant.prototype.setCheckedToNextItem = function (currentItem) { +RadioGroupActiveDescendant.prototype.setCheckedToNextItem = function ( + currentItem +) { var index; if (currentItem === this.lastRadioButton) { this.setChecked(this.firstRadioButton); - } - else { + } else { index = this.radioButtons.indexOf(currentItem); this.setChecked(this.radioButtons[index + 1]); } @@ -80,7 +81,10 @@ RadioGroupActiveDescendant.prototype.setCheckedToNextItem = function (currentIte RadioGroupActiveDescendant.prototype.getCurrentRadioButton = function () { var id = this.groupNode.getAttribute('aria-activedescendant'); if (!id) { - this.groupNode.setAttribute('aria-activedescendant', this.firstRadioButton.id); + this.groupNode.setAttribute( + 'aria-activedescendant', + this.firstRadioButton.id + ); return this.firstRadioButton; } for (var i = 0; i < this.radioButtons.length; i++) { @@ -89,7 +93,10 @@ RadioGroupActiveDescendant.prototype.getCurrentRadioButton = function () { return rb; } } - this.groupNode.setAttribute('aria-activedescendant', this.firstRadioButton.id); + this.groupNode.setAttribute( + 'aria-activedescendant', + this.firstRadioButton.id + ); return this.firstRadioButton; }; @@ -146,12 +153,11 @@ RadioGroupActiveDescendant.prototype.handleBlur = function () { currentItem.classList.remove('focus'); }; - // Initialize radio button group using aria-activedescendant window.addEventListener('load', function () { var rgs = document.querySelectorAll('.radiogroup-activedescendant'); - for(var i=0; i < rgs.length; i++) { + for (var i = 0; i < rgs.length; i++) { new RadioGroupActiveDescendant(rgs[i]); } }); diff --git a/examples/radio/js/radio.js b/examples/radio/js/radio.js index b50b4480dd..ddf887df69 100644 --- a/examples/radio/js/radio.js +++ b/examples/radio/js/radio.js @@ -1,22 +1,21 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: radio.js -* -* Desc: Radio group widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: radio.js + * + * Desc: Radio group widget that implements ARIA Authoring Practices + */ 'use strict'; var RadioGroup = function (groupNode) { - this.groupNode = groupNode; this.radioButtons = []; - this.firstRadioButton = null; - this.lastRadioButton = null; + this.firstRadioButton = null; + this.lastRadioButton = null; var rbs = this.groupNode.querySelectorAll('[role=radio]'); @@ -26,10 +25,10 @@ var RadioGroup = function (groupNode) { rb.tabIndex = -1; rb.setAttribute('aria-checked', 'false'); - rb.addEventListener('keydown', this.handleKeydown.bind(this)); - rb.addEventListener('click', this.handleClick.bind(this)); - rb.addEventListener('focus', this.handleFocus.bind(this)); - rb.addEventListener('blur', this.handleBlur.bind(this)); + rb.addEventListener('keydown', this.handleKeydown.bind(this)); + rb.addEventListener('click', this.handleClick.bind(this)); + rb.addEventListener('focus', this.handleFocus.bind(this)); + rb.addEventListener('blur', this.handleBlur.bind(this)); this.radioButtons.push(rb); @@ -41,7 +40,7 @@ var RadioGroup = function (groupNode) { this.firstRadioButton.tabIndex = 0; }; -RadioGroup.prototype.setChecked = function (currentItem) { +RadioGroup.prototype.setChecked = function (currentItem) { for (var i = 0; i < this.radioButtons.length; i++) { var rb = this.radioButtons[i]; rb.setAttribute('aria-checked', 'false'); @@ -57,8 +56,7 @@ RadioGroup.prototype.setCheckedToPreviousItem = function (currentItem) { if (currentItem === this.firstRadioButton) { this.setChecked(this.lastRadioButton); - } - else { + } else { index = this.radioButtons.indexOf(currentItem); this.setChecked(this.radioButtons[index - 1]); } @@ -69,8 +67,7 @@ RadioGroup.prototype.setCheckedToNextItem = function (currentItem) { if (currentItem === this.lastRadioButton) { this.setChecked(this.firstRadioButton); - } - else { + } else { index = this.radioButtons.indexOf(currentItem); this.setChecked(this.radioButtons[index + 1]); } @@ -127,12 +124,11 @@ RadioGroup.prototype.handleBlur = function (event) { event.currentTarget.classList.remove('focus'); }; - // Initialize radio button group window.addEventListener('load', function () { var rgs = document.querySelectorAll('[role="radiogroup"]'); - for(var i=0; i < rgs.length; i++) { + for (var i = 0; i < rgs.length; i++) { new RadioGroup(rgs[i]); } }); diff --git a/examples/slider/js/multithumb-slider.js b/examples/slider/js/multithumb-slider.js index 6a19391e56..921d59084a 100644 --- a/examples/slider/js/multithumb-slider.js +++ b/examples/slider/js/multithumb-slider.js @@ -1,244 +1,235 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: slider.js -* -* Desc: Slider widget that implements ARIA Authoring Practices -*/ - -'use strict'; - -// Create Slider that contains value, valuemin, valuemax, and valuenow -var Slider = function (domNode) { - - this.domNode = domNode; - this.railDomNode = domNode.parentNode; - - this.labelDomNode = false; - this.minDomNode = false; - this.maxDomNode = false; - - this.valueNow = 50; - - this.railMin = 0; - this.railMax = 100; - this.railWidth = 0; - this.railBorderWidth = 1; - - this.thumbWidth = 20; - this.thumbHeight = 24; - - this.keyCode = Object.freeze({ - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - 'pageUp': 33, - 'pageDown': 34, - 'end': 35, - 'home': 36 - }); -}; - -// Initialize slider -Slider.prototype.init = function () { - - if (this.domNode.previousElementSibling) { - this.minDomNode = this.domNode.previousElementSibling; - this.railMin = parseInt((this.minDomNode.getAttribute('aria-valuemin'))); - } - else { - this.railMin = parseInt((this.domNode.getAttribute('aria-valuemin'))); - } - - if (this.domNode.nextElementSibling) { - this.maxDomNode = this.domNode.nextElementSibling; - this.railMax = parseInt((this.maxDomNode.getAttribute('aria-valuemax'))); - } - - else { - this.railMax = parseInt((this.domNode.getAttribute('aria-valuemax'))); - } - - this.valueNow = parseInt((this.domNode.getAttribute('aria-valuenow'))); - - this.railWidth = parseInt(this.railDomNode.style.width.slice(0, -2)); - - if (this.domNode.classList.contains('min')) { - this.labelDomNode = this.domNode.parentElement.previousElementSibling; - } - - if (this.domNode.classList.contains('max')) { - this.labelDomNode = this.domNode.parentElement.nextElementSibling; - } - - if (this.domNode.tabIndex != 0) { - this.domNode.tabIndex = 0; - } - - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - - this.moveSliderTo(this.valueNow); - -}; - -Slider.prototype.moveSliderTo = function (value) { - var valueMax = parseInt(this.domNode.getAttribute('aria-valuemax')); - var valueMin = parseInt(this.domNode.getAttribute('aria-valuemin')); - - if (value > valueMax) { - value = valueMax; - } - - if (value < valueMin) { - value = valueMin; - } - - this.valueNow = value; - this.dolValueNow = '$' + value; - - this.domNode.setAttribute('aria-valuenow', this.valueNow); - this.domNode.setAttribute('aria-valuetext', this.dolValueNow); - - if (this.minDomNode) { - this.minDomNode.setAttribute('aria-valuemax', this.valueNow); - } - - if (this.maxDomNode) { - this.maxDomNode.setAttribute('aria-valuemin', this.valueNow); - } - - var pos = Math.round(((this.valueNow - this.railMin) * (this.railWidth - 2 * (this.thumbWidth - this.railBorderWidth))) / (this.railMax - this.railMin)); - - if (this.minDomNode) { - this.domNode.style.left = (pos + this.thumbWidth - this.railBorderWidth) + 'px'; - } - else { - this.domNode.style.left = (pos - this.railBorderWidth) + 'px'; - } - - if (this.labelDomNode) { - this.labelDomNode.innerHTML = this.dolValueNow.toString(); - } -}; - -Slider.prototype.handleKeyDown = function (event) { - - var flag = false; - - switch (event.keyCode) { - case this.keyCode.left: - case this.keyCode.down: - this.moveSliderTo(this.valueNow - 1); - flag = true; - break; - - case this.keyCode.right: - case this.keyCode.up: - this.moveSliderTo(this.valueNow + 1); - flag = true; - break; - - case this.keyCode.pageDown: - this.moveSliderTo(this.valueNow - 10); - flag = true; - break; - - case this.keyCode.pageUp: - this.moveSliderTo(this.valueNow + 10); - flag = true; - break; - - case this.keyCode.home: - this.moveSliderTo(this.railMin); - flag = true; - break; - - case this.keyCode.end: - this.moveSliderTo(this.railMax); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.preventDefault(); - event.stopPropagation(); - } - -}; - -Slider.prototype.handleFocus = function (event) { - this.domNode.classList.add('focus'); - this.railDomNode.classList.add('focus'); -}; - -Slider.prototype.handleBlur = function (event) { - this.domNode.classList.remove('focus'); - this.railDomNode.classList.remove('focus'); -}; - -Slider.prototype.handleMouseDown = function (event) { - - var self = this; - - var handleMouseMove = function (event) { - - var diffX = event.pageX - self.railDomNode.offsetLeft; - self.valueNow = self.railMin + parseInt(((self.railMax - self.railMin) * diffX) / self.railWidth); - self.moveSliderTo(self.valueNow); - - event.preventDefault(); - event.stopPropagation(); - }; - - var handleMouseUp = function (event) { - - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - - }; - - // bind a mousemove event handler to move pointer - document.addEventListener('mousemove', handleMouseMove); - - // bind a mouseup event handler to stop tracking mouse movements - document.addEventListener('mouseup', handleMouseUp); - - event.preventDefault(); - event.stopPropagation(); - - // Set focus to the clicked handle - this.domNode.focus(); - -}; - -// handleMouseMove has the same functionality as we need for handleMouseClick on the rail -// Slider.prototype.handleClick = function (event) { - -// var diffX = event.pageX - this.railDomNode.offsetLeft; -// this.valueNow = parseInt(((this.railMax - this.railMin) * diffX) / this.railWidth); -// this.moveSliderTo(this.valueNow); - -// event.preventDefault(); -// event.stopPropagation(); - -// }; - -// Initialise Sliders on the page -window.addEventListener('load', function () { - - var sliders = document.querySelectorAll('[role=slider]'); - - for (var i = 0; i < sliders.length; i++) { - var s = new Slider(sliders[i]); - s.init(); - } - -}); +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: slider.js + * + * Desc: Slider widget that implements ARIA Authoring Practices + */ + +'use strict'; + +// Create Slider that contains value, valuemin, valuemax, and valuenow +var Slider = function (domNode) { + this.domNode = domNode; + this.railDomNode = domNode.parentNode; + + this.labelDomNode = false; + this.minDomNode = false; + this.maxDomNode = false; + + this.valueNow = 50; + + this.railMin = 0; + this.railMax = 100; + this.railWidth = 0; + this.railBorderWidth = 1; + + this.thumbWidth = 20; + this.thumbHeight = 24; + + this.keyCode = Object.freeze({ + left: 37, + up: 38, + right: 39, + down: 40, + pageUp: 33, + pageDown: 34, + end: 35, + home: 36, + }); +}; + +// Initialize slider +Slider.prototype.init = function () { + if (this.domNode.previousElementSibling) { + this.minDomNode = this.domNode.previousElementSibling; + this.railMin = parseInt(this.minDomNode.getAttribute('aria-valuemin')); + } else { + this.railMin = parseInt(this.domNode.getAttribute('aria-valuemin')); + } + + if (this.domNode.nextElementSibling) { + this.maxDomNode = this.domNode.nextElementSibling; + this.railMax = parseInt(this.maxDomNode.getAttribute('aria-valuemax')); + } else { + this.railMax = parseInt(this.domNode.getAttribute('aria-valuemax')); + } + + this.valueNow = parseInt(this.domNode.getAttribute('aria-valuenow')); + + this.railWidth = parseInt(this.railDomNode.style.width.slice(0, -2)); + + if (this.domNode.classList.contains('min')) { + this.labelDomNode = this.domNode.parentElement.previousElementSibling; + } + + if (this.domNode.classList.contains('max')) { + this.labelDomNode = this.domNode.parentElement.nextElementSibling; + } + + if (this.domNode.tabIndex != 0) { + this.domNode.tabIndex = 0; + } + + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + + this.moveSliderTo(this.valueNow); +}; + +Slider.prototype.moveSliderTo = function (value) { + var valueMax = parseInt(this.domNode.getAttribute('aria-valuemax')); + var valueMin = parseInt(this.domNode.getAttribute('aria-valuemin')); + + if (value > valueMax) { + value = valueMax; + } + + if (value < valueMin) { + value = valueMin; + } + + this.valueNow = value; + this.dolValueNow = '$' + value; + + this.domNode.setAttribute('aria-valuenow', this.valueNow); + this.domNode.setAttribute('aria-valuetext', this.dolValueNow); + + if (this.minDomNode) { + this.minDomNode.setAttribute('aria-valuemax', this.valueNow); + } + + if (this.maxDomNode) { + this.maxDomNode.setAttribute('aria-valuemin', this.valueNow); + } + + var pos = Math.round( + ((this.valueNow - this.railMin) * + (this.railWidth - 2 * (this.thumbWidth - this.railBorderWidth))) / + (this.railMax - this.railMin) + ); + + if (this.minDomNode) { + this.domNode.style.left = + pos + this.thumbWidth - this.railBorderWidth + 'px'; + } else { + this.domNode.style.left = pos - this.railBorderWidth + 'px'; + } + + if (this.labelDomNode) { + this.labelDomNode.innerHTML = this.dolValueNow.toString(); + } +}; + +Slider.prototype.handleKeyDown = function (event) { + var flag = false; + + switch (event.keyCode) { + case this.keyCode.left: + case this.keyCode.down: + this.moveSliderTo(this.valueNow - 1); + flag = true; + break; + + case this.keyCode.right: + case this.keyCode.up: + this.moveSliderTo(this.valueNow + 1); + flag = true; + break; + + case this.keyCode.pageDown: + this.moveSliderTo(this.valueNow - 10); + flag = true; + break; + + case this.keyCode.pageUp: + this.moveSliderTo(this.valueNow + 10); + flag = true; + break; + + case this.keyCode.home: + this.moveSliderTo(this.railMin); + flag = true; + break; + + case this.keyCode.end: + this.moveSliderTo(this.railMax); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } +}; + +Slider.prototype.handleFocus = function (event) { + this.domNode.classList.add('focus'); + this.railDomNode.classList.add('focus'); +}; + +Slider.prototype.handleBlur = function (event) { + this.domNode.classList.remove('focus'); + this.railDomNode.classList.remove('focus'); +}; + +Slider.prototype.handleMouseDown = function (event) { + var self = this; + + var handleMouseMove = function (event) { + var diffX = event.pageX - self.railDomNode.offsetLeft; + self.valueNow = + self.railMin + + parseInt(((self.railMax - self.railMin) * diffX) / self.railWidth); + self.moveSliderTo(self.valueNow); + + event.preventDefault(); + event.stopPropagation(); + }; + + var handleMouseUp = function (event) { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + // bind a mousemove event handler to move pointer + document.addEventListener('mousemove', handleMouseMove); + + // bind a mouseup event handler to stop tracking mouse movements + document.addEventListener('mouseup', handleMouseUp); + + event.preventDefault(); + event.stopPropagation(); + + // Set focus to the clicked handle + this.domNode.focus(); +}; + +// handleMouseMove has the same functionality as we need for handleMouseClick on the rail +// Slider.prototype.handleClick = function (event) { + +// var diffX = event.pageX - this.railDomNode.offsetLeft; +// this.valueNow = parseInt(((this.railMax - this.railMin) * diffX) / this.railWidth); +// this.moveSliderTo(this.valueNow); + +// event.preventDefault(); +// event.stopPropagation(); + +// }; + +// Initialise Sliders on the page +window.addEventListener('load', function () { + var sliders = document.querySelectorAll('[role=slider]'); + + for (var i = 0; i < sliders.length; i++) { + var s = new Slider(sliders[i]); + s.init(); + } +}); diff --git a/examples/slider/js/slider.js b/examples/slider/js/slider.js index 4b0ee4a0b6..5d34977c86 100644 --- a/examples/slider/js/slider.js +++ b/examples/slider/js/slider.js @@ -1,17 +1,16 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: slider.js -* -* Desc: Slider widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: slider.js + * + * Desc: Slider widget that implements ARIA Authoring Practices + */ 'use strict'; // Create Slider that contains value, valuemin, valuemax, and valuenow -var Slider = function (domNode) { - +var Slider = function (domNode) { this.domNode = domNode; this.railDomNode = domNode.parentNode; @@ -23,32 +22,31 @@ var Slider = function (domNode) { this.railWidth = 0; - this.thumbWidth = 8; + this.thumbWidth = 8; this.thumbHeight = 28; this.keyCode = Object.freeze({ - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - 'pageUp': 33, - 'pageDown': 34, - 'end': 35, - 'home': 36 + left: 37, + up: 38, + right: 39, + down: 40, + pageUp: 33, + pageDown: 34, + end: 35, + home: 36, }); }; // Initialize slider Slider.prototype.init = function () { - if (this.domNode.getAttribute('aria-valuemin')) { - this.valueMin = parseInt((this.domNode.getAttribute('aria-valuemin'))); + this.valueMin = parseInt(this.domNode.getAttribute('aria-valuemin')); } if (this.domNode.getAttribute('aria-valuemax')) { - this.valueMax = parseInt((this.domNode.getAttribute('aria-valuemax'))); + this.valueMax = parseInt(this.domNode.getAttribute('aria-valuemax')); } if (this.domNode.getAttribute('aria-valuenow')) { - this.valueNow = parseInt((this.domNode.getAttribute('aria-valuenow'))); + this.valueNow = parseInt(this.domNode.getAttribute('aria-valuenow')); } this.railWidth = parseInt(this.railDomNode.style.width.slice(0, -2)); @@ -56,10 +54,10 @@ Slider.prototype.init = function () { this.valueDomNode = this.railDomNode.nextElementSibling; if (this.valueDomNode) { - this.valueDomNode.innerHTML = '0'; - this.valueDomNode.style.left = (this.railDomNode.offsetLeft + this.railWidth + 10) + 'px'; - this.valueDomNode.style.top = (this.railDomNode.offsetTop - 8) + 'px'; + this.valueDomNode.style.left = + this.railDomNode.offsetLeft + this.railWidth + 10 + 'px'; + this.valueDomNode.style.top = this.railDomNode.offsetTop - 8 + 'px'; } if (this.domNode.tabIndex != 0) { @@ -68,23 +66,21 @@ Slider.prototype.init = function () { this.domNode.style.width = this.thumbWidth + 'px'; this.domNode.style.height = this.thumbHeight + 'px'; - this.domNode.style.top = (this.thumbHeight / -2) + 'px'; + this.domNode.style.top = this.thumbHeight / -2 + 'px'; - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); // add onmousedown, move, and onmouseup this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); this.railDomNode.addEventListener('click', this.handleClick.bind(this)); this.moveSliderTo(this.valueNow); - }; Slider.prototype.moveSliderTo = function (value) { - if (value > this.valueMax) { value = this.valueMax; } @@ -97,9 +93,11 @@ Slider.prototype.moveSliderTo = function (value) { this.domNode.setAttribute('aria-valuenow', this.valueNow); - var pos = Math.round( - (this.valueNow * this.railWidth) / (this.valueMax - this.valueMin) - ) - (this.thumbWidth / 2); + var pos = + Math.round( + (this.valueNow * this.railWidth) / (this.valueMax - this.valueMin) + ) - + this.thumbWidth / 2; this.domNode.style.left = pos + 'px'; @@ -108,11 +106,9 @@ Slider.prototype.moveSliderTo = function (value) { } updateColorBox(); - }; Slider.prototype.handleKeyDown = function (event) { - var flag = false; switch (event.keyCode) { @@ -156,7 +152,6 @@ Slider.prototype.handleKeyDown = function (event) { event.preventDefault(); event.stopPropagation(); } - }; Slider.prototype.handleFocus = function (event) { @@ -171,24 +166,22 @@ Slider.prototype.handleBlur = function (event) { // Initialise Sliders on the page window.addEventListener('load', function () { - var sliders = document.querySelectorAll('[role=slider]'); for (var i = 0; i < sliders.length; i++) { var s = new Slider(sliders[i]); s.init(); } - }); Slider.prototype.handleMouseDown = function (event) { - var self = this; var handleMouseMove = function (event) { - var diffX = event.pageX - self.railDomNode.offsetLeft; - self.valueNow = parseInt(((self.valueMax - self.valueMin) * diffX) / self.railWidth); + self.valueNow = parseInt( + ((self.valueMax - self.valueMin) * diffX) / self.railWidth + ); self.moveSliderTo(self.valueNow); event.preventDefault(); @@ -196,10 +189,8 @@ Slider.prototype.handleMouseDown = function (event) { }; var handleMouseUp = function (event) { - document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); - }; // bind a mousemove event handler to move pointer @@ -213,19 +204,18 @@ Slider.prototype.handleMouseDown = function (event) { // Set focus to the clicked handle this.domNode.focus(); - }; // handleMouseMove has the same functionality as we need for handleMouseClick on the rail Slider.prototype.handleClick = function (event) { - var diffX = event.pageX - this.railDomNode.offsetLeft; - this.valueNow = parseInt(((this.valueMax - this.valueMin) * diffX) / this.railWidth); + this.valueNow = parseInt( + ((this.valueMax - this.valueMin) * diffX) / this.railWidth + ); this.moveSliderTo(this.valueNow); event.preventDefault(); event.stopPropagation(); - }; /* ---------------------------------------------------------------- */ @@ -233,11 +223,16 @@ Slider.prototype.handleClick = function (event) { /* ---------------------------------------------------------------- */ function updateColorBox() { - - function getColorHex () { - var r = parseInt(document.getElementById('idRedValue').getAttribute('aria-valuenow')).toString(16); - var g = parseInt(document.getElementById('idGreenValue').getAttribute('aria-valuenow')).toString(16); - var b = parseInt(document.getElementById('idBlueValue').getAttribute('aria-valuenow')).toString(16); + function getColorHex() { + var r = parseInt( + document.getElementById('idRedValue').getAttribute('aria-valuenow') + ).toString(16); + var g = parseInt( + document.getElementById('idGreenValue').getAttribute('aria-valuenow') + ).toString(16); + var b = parseInt( + document.getElementById('idBlueValue').getAttribute('aria-valuenow') + ).toString(16); if (r.length === 1) { r = '0' + r; @@ -252,10 +247,14 @@ function updateColorBox() { return '#' + r + g + b; } - function getColorRGB () { + function getColorRGB() { var r = document.getElementById('idRedValue').getAttribute('aria-valuenow'); - var g = document.getElementById('idGreenValue').getAttribute('aria-valuenow'); - var b = document.getElementById('idBlueValue').getAttribute('aria-valuenow'); + var g = document + .getElementById('idGreenValue') + .getAttribute('aria-valuenow'); + var b = document + .getElementById('idBlueValue') + .getAttribute('aria-valuenow'); return r + ', ' + g + ', ' + b; } @@ -263,7 +262,6 @@ function updateColorBox() { var node = document.getElementById('idColorBox'); if (node) { - var color = getColorHex(); node.style.backgroundColor = color; @@ -273,6 +271,5 @@ function updateColorBox() { node = document.getElementById('idColorValueRGB'); node.value = getColorRGB(); - } } diff --git a/examples/slider/js/text-slider.js b/examples/slider/js/text-slider.js index cd16a78380..dde4864fc9 100644 --- a/examples/slider/js/text-slider.js +++ b/examples/slider/js/text-slider.js @@ -1,18 +1,17 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: text-slider.js -* -* Desc: Text slider widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: text-slider.js + * + * Desc: Text slider widget that implements ARIA Authoring Practices + */ 'use strict'; // Create Text Slider that contains value, valuemin, valuemax, and valuenow -var TSlider = function (domNode) { - +var TSlider = function (domNode) { this.domNode = domNode; this.railDomNode = domNode.parentNode; @@ -31,30 +30,29 @@ var TSlider = function (domNode) { this.railWidth = 0; - this.thumbWidth = 8; + this.thumbWidth = 8; this.thumbHeight = 28; this.keyCode = Object.freeze({ - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - 'end': 35, - 'home': 36 + left: 37, + up: 38, + right: 39, + down: 40, + end: 35, + home: 36, }); }; // Initialize text slider TSlider.prototype.init = function () { - if (this.domNode.getAttribute('aria-valuemin')) { - this.valueMin = parseInt((this.domNode.getAttribute('aria-valuemin'))); + this.valueMin = parseInt(this.domNode.getAttribute('aria-valuemin')); } if (this.domNode.getAttribute('aria-valuemax')) { - this.valueMax = parseInt((this.domNode.getAttribute('aria-valuemax'))); + this.valueMax = parseInt(this.domNode.getAttribute('aria-valuemax')); } if (this.domNode.getAttribute('aria-valuenow')) { - this.valueNow = parseInt((this.domNode.getAttribute('aria-valuenow'))); + this.valueNow = parseInt(this.domNode.getAttribute('aria-valuenow')); } this.railWidth = parseInt(this.railDomNode.style.width.slice(0, -2)); @@ -65,33 +63,29 @@ TSlider.prototype.init = function () { this.domNode.style.width = this.thumbWidth + 'px'; this.domNode.style.height = this.thumbHeight + 'px'; - this.domNode.style.top = (this.thumbHeight / -2) + 'px'; + this.domNode.style.top = this.thumbHeight / -2 + 'px'; var pos = 0; var diff = this.railWidth / (this.valueNodes.length - 1); for (var i = 0; this.valueNodes[i]; i++) { - - this.valueNodes[i].style.left = ( - pos - (this.valueNodes[i].offsetWidth / 2) - ) + 'px'; + this.valueNodes[i].style.left = + pos - this.valueNodes[i].offsetWidth / 2 + 'px'; pos = pos + diff; } - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); // add onmousedown, move, and onmouseup this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); this.railDomNode.addEventListener('click', this.handleClick.bind(this)); this.moveTSliderTo(this.valueNow); - }; TSlider.prototype.moveTSliderTo = function (value) { - if (value > this.valueMax) { value = this.valueMax; } @@ -105,16 +99,16 @@ TSlider.prototype.moveTSliderTo = function (value) { this.domNode.setAttribute('aria-valuenow', this.valueNow); this.domNode.setAttribute('aria-valuetext', this.values[this.valueNow]); - var pos = Math.round( - (this.valueNow * this.railWidth) / (this.valueMax - this.valueMin) - ) - (this.thumbWidth / 2); + var pos = + Math.round( + (this.valueNow * this.railWidth) / (this.valueMax - this.valueMin) + ) - + this.thumbWidth / 2; this.domNode.style.left = pos + 'px'; - }; TSlider.prototype.handleKeyDown = function (event) { - var flag = false; switch (event.keyCode) { @@ -147,7 +141,6 @@ TSlider.prototype.handleKeyDown = function (event) { event.preventDefault(); event.stopPropagation(); } - }; TSlider.prototype.handleFocus = function (event) { @@ -161,13 +154,13 @@ TSlider.prototype.handleBlur = function (event) { }; TSlider.prototype.handleMouseDown = function (event) { - var self = this; var handleMouseMove = function (event) { - var diffX = event.pageX - self.railDomNode.offsetLeft; - self.valueNow = parseInt(((self.valueMax - self.valueMin) * diffX) / self.railWidth); + self.valueNow = parseInt( + ((self.valueMax - self.valueMin) * diffX) / self.railWidth + ); self.moveTSliderTo(self.valueNow); event.preventDefault(); @@ -175,10 +168,8 @@ TSlider.prototype.handleMouseDown = function (event) { }; var handleMouseUp = function (event) { - document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); - }; // bind a mousemove event handler to move pointer @@ -192,29 +183,28 @@ TSlider.prototype.handleMouseDown = function (event) { // Set focus to the clicked handle this.domNode.focus(); - }; // handleMouseMove has the same functionality as we need for handleMouseClick on the rail TSlider.prototype.handleClick = function (event) { - var diffX = event.pageX - this.railDomNode.offsetLeft; - this.valueNow = parseInt(((this.valueMax - this.valueMin) * diffX) / this.railWidth); + this.valueNow = parseInt( + ((this.valueMax - this.valueMin) * diffX) / this.railWidth + ); this.moveTSliderTo(this.valueNow); event.preventDefault(); event.stopPropagation(); - }; // Initialise TSliders on the page window.addEventListener('load', function () { - - var sliders = document.querySelectorAll('.aria-widget-text-slider [role=slider]'); + var sliders = document.querySelectorAll( + '.aria-widget-text-slider [role=slider]' + ); for (var i = 0; i < sliders.length; i++) { var s = new TSlider(sliders[i]); s.init(); } - }); diff --git a/examples/slider/js/vertical-slider.js b/examples/slider/js/vertical-slider.js index b6e3089306..046671a54a 100644 --- a/examples/slider/js/vertical-slider.js +++ b/examples/slider/js/vertical-slider.js @@ -1,17 +1,16 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: vertical-slider.js -* -* Desc: Vertical slider widget that implements ARIA Authoring Practices -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: vertical-slider.js + * + * Desc: Vertical slider widget that implements ARIA Authoring Practices + */ 'use strict'; // Create Vertical Slider that contains value, valuemin, valuemax, and valuenow -var VSlider = function (domNode) { - +var VSlider = function (domNode) { this.domNode = domNode; this.railDomNode = domNode.parentNode; @@ -23,34 +22,33 @@ var VSlider = function (domNode) { this.railHeight = 0; - this.thumbWidth = 28; + this.thumbWidth = 28; this.thumbHeight = 8; this.keyCode = Object.freeze({ - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - 'pageUp': 33, - 'pageDown': 34, - 'end': 35, - 'home': 36 + left: 37, + up: 38, + right: 39, + down: 40, + pageUp: 33, + pageDown: 34, + end: 35, + home: 36, }); }; // Initialize vertical slider VSlider.prototype.init = function () { - this.domNode.setAttribute('aria-orientation', 'vertical'); if (this.domNode.getAttribute('aria-valuemin')) { - this.valueMin = parseInt((this.domNode.getAttribute('aria-valuemin'))); + this.valueMin = parseInt(this.domNode.getAttribute('aria-valuemin')); } if (this.domNode.getAttribute('aria-valuemax')) { - this.valueMax = parseInt((this.domNode.getAttribute('aria-valuemax'))); + this.valueMax = parseInt(this.domNode.getAttribute('aria-valuemax')); } if (this.domNode.getAttribute('aria-valuenow')) { - this.valueNow = parseInt((this.domNode.getAttribute('aria-valuenow'))); + this.valueNow = parseInt(this.domNode.getAttribute('aria-valuenow')); } this.railHeight = parseInt(this.railDomNode.style.height.slice(0, -2)); @@ -67,23 +65,21 @@ VSlider.prototype.init = function () { this.domNode.style.width = this.thumbWidth + 'px'; this.domNode.style.height = this.thumbHeight + 'px'; - this.domNode.style.left = (this.thumbWidth / -2) + 'px'; + this.domNode.style.left = this.thumbWidth / -2 + 'px'; - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); // add onmousedown, move, and onmouseup this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); this.railDomNode.addEventListener('click', this.handleClick.bind(this)); this.moveVSliderTo(this.valueNow); - }; VSlider.prototype.moveVSliderTo = function (value) { - if (value > this.valueMax) { value = this.valueMax; } @@ -97,23 +93,23 @@ VSlider.prototype.moveVSliderTo = function (value) { this.domNode.setAttribute('aria-valuenow', this.valueNow); this.domNode.setAttribute('aria-valuetext', this.valueNow + ' degrees'); - var pos = Math.round( - ((this.valueMax - this.valueNow) * this.railHeight - ) / (this.valueMax - this.valueMin) - ) - (this.thumbHeight / 2); + var pos = + Math.round( + ((this.valueMax - this.valueNow) * this.railHeight) / + (this.valueMax - this.valueMin) + ) - + this.thumbHeight / 2; this.domNode.style.top = pos + 'px'; if (this.valueDomNode) { this.valueDomNode.innerHTML = this.valueNow.toString(); - this.valueDomNode.style.left = (this.railDomNode.offsetWidth) / 2 - 2 + 'px'; + this.valueDomNode.style.left = this.railDomNode.offsetWidth / 2 - 2 + 'px'; console.log(this.valueDomNode.style.left); } - }; VSlider.prototype.handleKeyDown = function (event) { - var flag = false; switch (event.keyCode) { @@ -157,7 +153,6 @@ VSlider.prototype.handleKeyDown = function (event) { event.preventDefault(); event.stopPropagation(); } - }; VSlider.prototype.handleFocus = function (event) { @@ -171,13 +166,13 @@ VSlider.prototype.handleBlur = function (event) { }; VSlider.prototype.handleMouseDown = function (event) { - var self = this; var handleMouseMove = function (event) { - var diffY = event.pageY - self.railDomNode.offsetTop; - self.valueNow = self.valueMax - parseInt(((self.valueMax - self.valueMin) * diffY) / self.railHeight); + self.valueNow = + self.valueMax - + parseInt(((self.valueMax - self.valueMin) * diffY) / self.railHeight); self.moveVSliderTo(self.valueNow); event.preventDefault(); @@ -185,10 +180,8 @@ VSlider.prototype.handleMouseDown = function (event) { }; var handleMouseUp = function (event) { - document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); - }; // bind a mousemove event handler to move pointer @@ -202,29 +195,28 @@ VSlider.prototype.handleMouseDown = function (event) { // Set focus to the clicked handle this.domNode.focus(); - }; // handleMouseMove has the same functionality as we need for handleMouseClick on the rail VSlider.prototype.handleClick = function (event) { - var diffY = event.pageY - this.railDomNode.offsetTop; - this.valueNow = this.valueMax - parseInt(((this.valueMax - this.valueMin) * diffY) / this.railHeight); + this.valueNow = + this.valueMax - + parseInt(((this.valueMax - this.valueMin) * diffY) / this.railHeight); this.moveVSliderTo(this.valueNow); event.preventDefault(); event.stopPropagation(); - }; // Initialise VSliders on the page window.addEventListener('load', function () { - - var sliders = document.querySelectorAll('.aria-widget-vertical-slider [role=slider]'); + var sliders = document.querySelectorAll( + '.aria-widget-vertical-slider [role=slider]' + ); for (var i = 0; i < sliders.length; i++) { var s = new VSlider(sliders[i]); s.init(); } - }); diff --git a/examples/spinbutton/js/datepicker-spinbuttons.js b/examples/spinbutton/js/datepicker-spinbuttons.js index c63290c767..033f928bd3 100644 --- a/examples/spinbutton/js/datepicker-spinbuttons.js +++ b/examples/spinbutton/js/datepicker-spinbuttons.js @@ -1,38 +1,101 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: datepicker-spinbuttons.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: datepicker-spinbuttons.js + */ 'use strict'; /* global SpinButtonDate */ -var DatePickerSpinButtons = function (domNode) { - +var DatePickerSpinButtons = function (domNode) { this.domNode = domNode; this.monthNode = domNode.querySelector('.spinbutton.month'); this.dayNode = domNode.querySelector('.spinbutton.day'); this.yearNode = domNode.querySelector('.spinbutton.year'); this.dateNode = domNode.querySelector('.date'); - this.valuesWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; - this.valuesDay = ['', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteen', 'fifteenth', 'sixteenth', 'seveneenth', 'eighteenth', 'nineteenth', 'twentieth', 'twenty first', 'twenty second', 'twenty third', 'twenty fourth', 'twenty fifth', 'twenty sixth', 'twenty seventh', 'twenty eighth', 'twenty ninth', 'thirtieth', 'thirty first']; - this.valuesMonth = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - + this.valuesWeek = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ]; + this.valuesDay = [ + '', + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'sixth', + 'seventh', + 'eighth', + 'ninth', + 'tenth', + 'eleventh', + 'twelfth', + 'thirteenth', + 'fourteen', + 'fifteenth', + 'sixteenth', + 'seveneenth', + 'eighteenth', + 'nineteenth', + 'twentieth', + 'twenty first', + 'twenty second', + 'twenty third', + 'twenty fourth', + 'twenty fifth', + 'twenty sixth', + 'twenty seventh', + 'twenty eighth', + 'twenty ninth', + 'thirtieth', + 'thirty first', + ]; + this.valuesMonth = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; }; // Initialize slider DatePickerSpinButtons.prototype.init = function () { - - this.spinbuttonDay = new SpinButtonDate(this.dayNode, null, this.updateDay.bind(this)); + this.spinbuttonDay = new SpinButtonDate( + this.dayNode, + null, + this.updateDay.bind(this) + ); this.spinbuttonDay.init(); - this.spinbuttonMonth = new SpinButtonDate(this.monthNode, this.valuesMonth, this.updateMonth.bind(this)); + this.spinbuttonMonth = new SpinButtonDate( + this.monthNode, + this.valuesMonth, + this.updateMonth.bind(this) + ); this.spinbuttonMonth.init(); - this.spinbuttonYear = new SpinButtonDate(this.yearNode, null, this.updateYear.bind(this)); + this.spinbuttonYear = new SpinButtonDate( + this.yearNode, + null, + this.updateYear.bind(this) + ); this.spinbuttonYear.init(); this.spinbuttonYear.noWrap(); @@ -95,7 +158,6 @@ DatePickerSpinButtons.prototype.updateNextDayMonthAndYear = function () { }; DatePickerSpinButtons.prototype.updateSpinButtons = function () { - this.daysInMonth = this.getDaysInMonth(this.year, this.month); this.spinbuttonDay.setValueMax(this.daysInMonth); if (this.day > this.daysInMonth) { @@ -116,22 +178,28 @@ DatePickerSpinButtons.prototype.updateSpinButtons = function () { this.spinbuttonMonth.setNextValue(this.nextMonth); this.spinbuttonYear.setNextValue(this.nextYear); - this.currentDate = new Date(this.year + '-' + (this.month + 1) + '-' + this.day); - - this.dateNode.innerHTML = 'current value is ' + this.valuesWeek[this.currentDate.getDay()] + ', ' + this.spinbuttonMonth.getValueText() + ' ' + this.spinbuttonDay.getValueText() + ', ' + this.spinbuttonYear.getValue(); - - + this.currentDate = new Date( + this.year + '-' + (this.month + 1) + '-' + this.day + ); + + this.dateNode.innerHTML = + 'current value is ' + + this.valuesWeek[this.currentDate.getDay()] + + ', ' + + this.spinbuttonMonth.getValueText() + + ' ' + + this.spinbuttonDay.getValueText() + + ', ' + + this.spinbuttonYear.getValue(); }; // Initialize menu button date picker -window.addEventListener('load' , function () { - +window.addEventListener('load', function () { var dpsbs = document.querySelectorAll('.datepicker-spinbuttons'); dpsbs.forEach(function (dpsb) { var datepicker = new DatePickerSpinButtons(dpsb); datepicker.init(); }); - }); diff --git a/examples/spinbutton/js/spinbutton-date.js b/examples/spinbutton/js/spinbutton-date.js index f4c48fc0a6..a99712ee85 100644 --- a/examples/spinbutton/js/spinbutton-date.js +++ b/examples/spinbutton/js/spinbutton-date.js @@ -1,14 +1,13 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: spinbutton-date.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: spinbutton-date.js + */ 'use strict'; -var SpinButtonDate = function (domNode, values, callback) { - +var SpinButtonDate = function (domNode, values, callback) { this.domNode = domNode; this.values = values; this.callback = callback; @@ -25,43 +24,49 @@ var SpinButtonDate = function (domNode, values, callback) { this.decreaseNode = domNode.querySelector('.decrease'); if (values) { - this.valueMin = 0; - this.valueMax = values.length - 1; + this.valueMin = 0; + this.valueMax = values.length - 1; if (initialValue) { - this.valueNow = values.indexOf(initialValue); + this.valueNow = values.indexOf(initialValue); this.valueText = initialValue; - } - else { - this.valueNow = values.length / 2; + } else { + this.valueNow = values.length / 2; this.valueText = values[this.valueNow]; } - } - else { - this.valueMin = parseInt(this.spinbuttonNode.getAttribute('aria-valuemin')); - this.valueMax = parseInt(this.spinbuttonNode.getAttribute('aria-valuemax')); - this.valueNow = parseInt(this.spinbuttonNode.getAttribute('aria-valuenow')); + } else { + this.valueMin = parseInt(this.spinbuttonNode.getAttribute('aria-valuemin')); + this.valueMax = parseInt(this.spinbuttonNode.getAttribute('aria-valuemax')); + this.valueNow = parseInt(this.spinbuttonNode.getAttribute('aria-valuenow')); this.valueText = this.spinbuttonNode.getAttribute('aria-valuenow'); } this.keyCode = Object.freeze({ - 'UP': 38, - 'DOWN': 40, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36 + UP: 38, + DOWN: 40, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, }); }; // Initialize slider SpinButtonDate.prototype.init = function () { - - this.spinbuttonNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - this.spinbuttonNode.addEventListener('focus', this.handleFocus.bind(this)); - this.spinbuttonNode.addEventListener('blur', this.handleBlur.bind(this)); - - this.increaseNode.addEventListener('click', this.handleIncreaseClick.bind(this)); - this.decreaseNode.addEventListener('click', this.handleDecreaseClick.bind(this)); + this.spinbuttonNode.addEventListener( + 'keydown', + this.handleKeyDown.bind(this) + ); + this.spinbuttonNode.addEventListener('focus', this.handleFocus.bind(this)); + this.spinbuttonNode.addEventListener('blur', this.handleBlur.bind(this)); + + this.increaseNode.addEventListener( + 'click', + this.handleIncreaseClick.bind(this) + ); + this.decreaseNode.addEventListener( + 'click', + this.handleDecreaseClick.bind(this) + ); }; SpinButtonDate.prototype.noWrap = function () { @@ -76,9 +81,7 @@ SpinButtonDate.prototype.getValueText = function () { return this.valueText; }; - SpinButtonDate.prototype.setValue = function (value, flag) { - if (typeof flag !== 'boolean') { flag = true; } @@ -86,8 +89,7 @@ SpinButtonDate.prototype.setValue = function (value, flag) { if (value > this.valueMax) { if (this.wrap) { value = this.valueMin; - } - else { + } else { value = this.valueMax; } } @@ -95,17 +97,15 @@ SpinButtonDate.prototype.setValue = function (value, flag) { if (value < this.valueMin) { if (this.wrap) { value = this.valueMax; - } - else { + } else { value = this.valueMin; } } - this.valueNow = value; + this.valueNow = value; if (this.values) { this.valueText = this.values[this.valueNow]; - } - else { + } else { if (typeof value === 'number') { this.valueText = parseInt(value); } @@ -119,10 +119,9 @@ SpinButtonDate.prototype.setValue = function (value, flag) { this.spinbuttonNode.innerHTML = this.valueText; - if (flag, this.callback) { + if ((flag, this.callback)) { this.callback(this.valueNow); } - }; SpinButtonDate.prototype.setValueText = function (value) { @@ -130,7 +129,6 @@ SpinButtonDate.prototype.setValueText = function (value) { this.spinbuttonNode.setAttribute('aria-valuetext', value); }; - SpinButtonDate.prototype.getValueMin = function () { return parseInt(this.spinbuttonNode.getAttribute('aria-valuemin')); }; @@ -144,9 +142,7 @@ SpinButtonDate.prototype.setValueMax = function (value) { this.valueMax = value; }; - SpinButtonDate.prototype.setPreviousValue = function (value) { - if (this.values) { value = this.values[value]; } @@ -154,8 +150,7 @@ SpinButtonDate.prototype.setPreviousValue = function (value) { if (typeof value === 'number') { if (value < this.valueMin) { value = ' '; - } - else { + } else { value = parseInt(value); } } @@ -171,8 +166,7 @@ SpinButtonDate.prototype.setNextValue = function (value) { if (typeof value === 'number') { if (value > this.valueMax) { value = ' '; - } - else { + } else { value = parseInt(value); } } @@ -181,7 +175,6 @@ SpinButtonDate.prototype.setNextValue = function (value) { }; SpinButtonDate.prototype.handleKeyDown = function (event) { - var flag = false; switch (event.keyCode) { @@ -223,7 +216,6 @@ SpinButtonDate.prototype.handleKeyDown = function (event) { event.preventDefault(); event.stopPropagation(); } - }; SpinButtonDate.prototype.handleFocus = function () { @@ -235,19 +227,15 @@ SpinButtonDate.prototype.handleBlur = function () { }; SpinButtonDate.prototype.handleIncreaseClick = function (event) { - this.setValue(this.valueNow + 1); event.preventDefault(); event.stopPropagation(); - }; SpinButtonDate.prototype.handleDecreaseClick = function (event) { - this.setValue(this.valueNow - 1); event.preventDefault(); event.stopPropagation(); - }; diff --git a/examples/tabs/tabs-1/js/tabs.js b/examples/tabs/tabs-1/js/tabs.js index c166e641fa..8b217bbbd3 100644 --- a/examples/tabs/tabs-1/js/tabs.js +++ b/examples/tabs/tabs-1/js/tabs.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -13,7 +13,7 @@ generateArrays(); - function generateArrays () { + function generateArrays() { tabs = document.querySelectorAll('[role="tab"]'); panels = document.querySelectorAll('[role="tabpanel"]'); } @@ -26,7 +26,7 @@ up: 38, right: 39, down: 40, - delete: 46 + delete: 46, }; // Add or substract depending on key pressed @@ -34,7 +34,7 @@ 37: -1, 38: -1, 39: 1, - 40: 1 + 40: 1, }; // Bind listeners @@ -42,7 +42,7 @@ addListeners(i); } - function addListeners (index) { + function addListeners(index) { tabs[index].addEventListener('click', clickEventListener); tabs[index].addEventListener('keydown', keydownEventListener); tabs[index].addEventListener('keyup', keyupEventListener); @@ -52,13 +52,13 @@ } // When a tab is clicked, activateTab is fired to activate it - function clickEventListener (event) { + function clickEventListener(event) { var tab = event.target; activateTab(tab, false); } // Handle keydown on tabs - function keydownEventListener (event) { + function keydownEventListener(event) { var key = event.keyCode; switch (key) { @@ -83,7 +83,7 @@ } // Handle keyup on tabs - function keyupEventListener (event) { + function keyupEventListener(event) { var key = event.keyCode; switch (key) { @@ -100,7 +100,7 @@ // When a tablist’s aria-orientation is set to vertical, // only up and down arrow should function. // In all other cases only left and right arrow function. - function determineOrientation (event) { + function determineOrientation(event) { var key = event.keyCode; var vertical = tablist.getAttribute('aria-orientation') == 'vertical'; var proceed = false; @@ -110,8 +110,7 @@ event.preventDefault(); proceed = true; } - } - else { + } else { if (key === keys.left || key === keys.right) { proceed = true; } @@ -124,7 +123,7 @@ // Either focus the next, previous, first, or last tab // depening on key pressed - function switchTabOnArrowPress (event) { + function switchTabOnArrowPress(event) { var pressed = event.keyCode; for (var x = 0; x < tabs.length; x++) { @@ -136,11 +135,9 @@ if (target.index !== undefined) { if (tabs[target.index + direction[pressed]]) { tabs[target.index + direction[pressed]].focus(); - } - else if (pressed === keys.left || pressed === keys.up) { + } else if (pressed === keys.left || pressed === keys.up) { focusLastTab(); - } - else if (pressed === keys.right || pressed == keys.down) { + } else if (pressed === keys.right || pressed == keys.down) { focusFirstTab(); } } @@ -148,7 +145,7 @@ } // Activates any given tab panel - function activateTab (tab, setFocus) { + function activateTab(tab, setFocus) { setFocus = setFocus || true; // Deactivate all other tabs deactivateTabs(); @@ -172,7 +169,7 @@ } // Deactivate all tabs and tab panels - function deactivateTabs () { + function deactivateTabs() { for (var t = 0; t < tabs.length; t++) { tabs[t].setAttribute('tabindex', '-1'); tabs[t].setAttribute('aria-selected', 'false'); @@ -185,17 +182,17 @@ } // Make a guess - function focusFirstTab () { + function focusFirstTab() { tabs[0].focus(); } // Make a guess - function focusLastTab () { + function focusLastTab() { tabs[tabs.length - 1].focus(); } // Detect if a tab is deletable - function determineDeletable (event) { + function determineDeletable(event) { var target = event.target; if (target.getAttribute('data-deletable') !== null) { @@ -208,15 +205,14 @@ // Activate the closest tab to the one that was just deleted if (target.index - 1 < 0) { activateTab(tabs[0]); - } - else { + } else { activateTab(tabs[target.index - 1]); } } } // Deletes a tab and its panel - function deleteTab (event) { + function deleteTab(event) { var target = event.target; var panel = document.getElementById(target.getAttribute('aria-controls')); @@ -226,7 +222,7 @@ // Determine whether there should be a delay // when user navigates with the arrow keys - function determineDelay () { + function determineDelay() { var hasDelay = tablist.hasAttribute('data-delay'); var delay = 0; @@ -234,8 +230,7 @@ var delayValue = tablist.getAttribute('data-delay'); if (delayValue) { delay = delayValue; - } - else { + } else { // If no value is specified, default to 300ms delay = 300; } @@ -245,18 +240,18 @@ } // - function focusEventHandler (event) { + function focusEventHandler(event) { var target = event.target; setTimeout(checkTabFocus, delay, target); } // Only activate tab on focus if it still has focus after the delay - function checkTabFocus (target) { + function checkTabFocus(target) { var focused = document.activeElement; if (target === focused) { activateTab(target, false); } } -}()); +})(); diff --git a/examples/tabs/tabs-2/js/tabs.js b/examples/tabs/tabs-2/js/tabs.js index 7c512d95c3..2b3eda488e 100644 --- a/examples/tabs/tabs-2/js/tabs.js +++ b/examples/tabs/tabs-2/js/tabs.js @@ -1,7 +1,7 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + */ 'use strict'; @@ -12,7 +12,7 @@ generateArrays(); - function generateArrays () { + function generateArrays() { tabs = document.querySelectorAll('[role="tab"]'); panels = document.querySelectorAll('[role="tabpanel"]'); } @@ -27,7 +27,7 @@ down: 40, delete: 46, enter: 13, - space: 32 + space: 32, }; // Add or subtract depending on key pressed @@ -35,7 +35,7 @@ 37: -1, 38: -1, 39: 1, - 40: 1 + 40: 1, }; // Bind listeners @@ -43,7 +43,7 @@ addListeners(i); } - function addListeners (index) { + function addListeners(index) { tabs[index].addEventListener('click', clickEventListener); tabs[index].addEventListener('keydown', keydownEventListener); tabs[index].addEventListener('keyup', keyupEventListener); @@ -53,13 +53,13 @@ } // When a tab is clicked, activateTab is fired to activate it - function clickEventListener (event) { + function clickEventListener(event) { var tab = event.target; activateTab(tab, false); } // Handle keydown on tabs - function keydownEventListener (event) { + function keydownEventListener(event) { var key = event.keyCode; switch (key) { @@ -84,7 +84,7 @@ } // Handle keyup on tabs - function keyupEventListener (event) { + function keyupEventListener(event) { var key = event.keyCode; switch (key) { @@ -105,7 +105,7 @@ // When a tablist’s aria-orientation is set to vertical, // only up and down arrow should function. // In all other cases only left and right arrow function. - function determineOrientation (event) { + function determineOrientation(event) { var key = event.keyCode; var vertical = tablist.getAttribute('aria-orientation') == 'vertical'; var proceed = false; @@ -115,8 +115,7 @@ event.preventDefault(); proceed = true; } - } - else { + } else { if (key === keys.left || key === keys.right) { proceed = true; } @@ -129,7 +128,7 @@ // Either focus the next, previous, first, or last tab // depending on key pressed - function switchTabOnArrowPress (event) { + function switchTabOnArrowPress(event) { var pressed = event.keyCode; if (direction[pressed]) { @@ -137,11 +136,9 @@ if (target.index !== undefined) { if (tabs[target.index + direction[pressed]]) { tabs[target.index + direction[pressed]].focus(); - } - else if (pressed === keys.left || pressed === keys.up) { + } else if (pressed === keys.left || pressed === keys.up) { focusLastTab(); - } - else if (pressed === keys.right || pressed == keys.down) { + } else if (pressed === keys.right || pressed == keys.down) { focusFirstTab(); } } @@ -149,7 +146,7 @@ } // Activates any given tab panel - function activateTab (tab, setFocus) { + function activateTab(tab, setFocus) { setFocus = setFocus || true; // Deactivate all other tabs deactivateTabs(); @@ -173,7 +170,7 @@ } // Deactivate all tabs and tab panels - function deactivateTabs () { + function deactivateTabs() { for (var t = 0; t < tabs.length; t++) { tabs[t].setAttribute('tabindex', '-1'); tabs[t].setAttribute('aria-selected', 'false'); @@ -185,17 +182,17 @@ } // Make a guess - function focusFirstTab () { + function focusFirstTab() { tabs[0].focus(); } // Make a guess - function focusLastTab () { + function focusLastTab() { tabs[tabs.length - 1].focus(); } // Detect if a tab is deletable - function determineDeletable (event) { + function determineDeletable(event) { var target = event.target; if (target.getAttribute('data-deletable') !== null) { @@ -208,15 +205,14 @@ // Activate the closest tab to the one that was just deleted if (target.index - 1 < 0) { activateTab(tabs[0]); - } - else { + } else { activateTab(tabs[target.index - 1]); } } } // Deletes a tab and its panel - function deleteTab (event) { + function deleteTab(event) { var target = event.target; var panel = document.getElementById(target.getAttribute('aria-controls')); @@ -226,7 +222,7 @@ // Determine whether there should be a delay // when user navigates with the arrow keys - function determineDelay () { + function determineDelay() { var hasDelay = tablist.hasAttribute('data-delay'); var delay = 0; @@ -234,8 +230,7 @@ var delayValue = tablist.getAttribute('data-delay'); if (delayValue) { delay = delayValue; - } - else { + } else { // If no value is specified, default to 300ms delay = 300; } @@ -243,4 +238,4 @@ return delay; } -}()); +})(); diff --git a/examples/toolbar/js/FontMenu.js b/examples/toolbar/js/FontMenu.js index 993d9b778d..75485ae17c 100644 --- a/examples/toolbar/js/FontMenu.js +++ b/examples/toolbar/js/FontMenu.js @@ -1,9 +1,9 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: FontMenu.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: FontMenu.js + */ 'use strict'; @@ -28,7 +28,9 @@ var FontMenu = function (domNode, controllerObj) { var menuitem = childElement.firstElementChild; if (menuitem && menuitem === 'A') { - throw new Error(msgPrefix + 'Cannot have descendant elements are A elements.'); + throw new Error( + msgPrefix + 'Cannot have descendant elements are A elements.' + ); } childElement = childElement.nextElementSibling; } @@ -36,24 +38,24 @@ var FontMenu = function (domNode, controllerObj) { this.domNode = domNode; this.controller = controllerObj; - this.menuitems = []; // See PopupMenu init method - this.firstChars = []; // See PopupMenu init method + this.menuitems = []; // See PopupMenu init method + this.firstChars = []; // See PopupMenu init method - this.firstItem = null; // See PopupMenu init method - this.lastItem = null; // See PopupMenu init method + this.firstItem = null; // See PopupMenu init method + this.lastItem = null; // See PopupMenu init method - this.hasFocus = false; // See MenuItem handleFocus, handleBlur - this.hasHover = false; // See PopupMenu handleMouseover, handleMouseout + this.hasFocus = false; // See MenuItem handleFocus, handleBlur + this.hasHover = false; // See PopupMenu handleMouseover, handleMouseout }; /* -* @method FontMenu.prototype.init -* -* @desc -* Add domNode event listeners for mouseover and mouseout. Traverse -* domNode children to configure each menuitem and populate menuitems -* array. Initialize firstItem and lastItem properties. -*/ + * @method FontMenu.prototype.init + * + * @desc + * Add domNode event listeners for mouseover and mouseout. Traverse + * domNode children to configure each menuitem and populate menuitems + * array. Initialize firstItem and lastItem properties. + */ FontMenu.prototype.init = function () { var menuitemElements, menuitemElement, menuItem, textContent, numItems; @@ -104,12 +106,10 @@ FontMenu.prototype.setFocusToController = function (command) { if (command === 'previous') { this.controller.toolbar.setFocusToPrevious(this.controller.toolbarItem); - } - else { + } else { if (command === 'next') { this.controller.toolbar.setFocusToNext(this.controller.toolbarItem); - } - else { + } else { this.controller.domNode.focus(); } } @@ -136,8 +136,7 @@ FontMenu.prototype.setFocusToPreviousItem = function (currentItem) { if (currentItem === this.firstItem) { this.lastItem.domNode.focus(); - } - else { + } else { index = this.menuitems.indexOf(currentItem); this.menuitems[index - 1].domNode.focus(); } @@ -148,8 +147,7 @@ FontMenu.prototype.setFocusToNextItem = function (currentItem) { if (currentItem === this.lastItem) { this.firstItem.domNode.focus(); - } - else { + } else { index = this.menuitems.indexOf(currentItem); this.menuitems[index + 1].domNode.focus(); } @@ -225,7 +223,7 @@ FontMenu.prototype.open = function () { // Set CSS properties this.domNode.style.display = 'block'; this.domNode.style.position = 'absolute'; - this.domNode.style.top = (rect.height - 1) + 'px'; + this.domNode.style.top = rect.height - 1 + 'px'; this.domNode.style.left = '0px'; this.domNode.style.zIndex = 100; @@ -238,7 +236,10 @@ FontMenu.prototype.close = function (force) { force = false; } - if (force || (!this.hasFocus && !this.hasHover && !this.controller.hasHover)) { + if ( + force || + (!this.hasFocus && !this.hasHover && !this.controller.hasHover) + ) { this.domNode.style.display = 'none'; this.controller.domNode.removeAttribute('aria-expanded'); } diff --git a/examples/toolbar/js/FontMenuButton.js b/examples/toolbar/js/FontMenuButton.js index 15ffb2ca6a..30d0d9b431 100644 --- a/examples/toolbar/js/FontMenuButton.js +++ b/examples/toolbar/js/FontMenuButton.js @@ -1,9 +1,9 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: FontMenuButton.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: FontMenuButton.js + */ 'use strict'; @@ -17,12 +17,12 @@ function FontMenuButton(node, toolbar, toolbarItem) { this.value = ''; this.keyCode = Object.freeze({ - 'TAB': 9, - 'ENTER': 13, - 'ESC': 27, - 'SPACE': 32, - 'UP': 38, - 'DOWN': 40 + TAB: 9, + ENTER: 13, + ESC: 27, + SPACE: 32, + UP: 38, + DOWN: 40, }); } @@ -68,8 +68,7 @@ FontMenuButton.prototype.handleKeyDown = function (event) { FontMenuButton.prototype.handleClick = function (event, menuButton) { if (this.fontMenu.isOpen()) { this.fontMenu.close(); - } - else { + } else { this.fontMenu.open(); } }; @@ -81,4 +80,3 @@ FontMenuButton.prototype.setFontFamily = function (font) { this.domNode.setAttribute('aria-label', 'Font: ' + font); this.toolbar.activateItem(this); }; - diff --git a/examples/toolbar/js/FontMenuItem.js b/examples/toolbar/js/FontMenuItem.js index 74e7e749e4..5fbd8ee4f9 100644 --- a/examples/toolbar/js/FontMenuItem.js +++ b/examples/toolbar/js/FontMenuItem.js @@ -1,46 +1,45 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: FontMenuItem.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: FontMenuItem.js + */ 'use strict'; /* -* @constructor MenuItem -* -* @desc -* Wrapper object for a simple menu item in a popup menu -* -* @param domNode -* The DOM element node that serves as the menu item container. -* The menuObj PopupMenu is responsible for checking that it has -* requisite metadata, e.g. role="menuitem". -* -* @param menuObj -* The object that is a wrapper for the PopupMenu DOM element that -* contains the menu item DOM element. See PopupMenuAction.js -*/ + * @constructor MenuItem + * + * @desc + * Wrapper object for a simple menu item in a popup menu + * + * @param domNode + * The DOM element node that serves as the menu item container. + * The menuObj PopupMenu is responsible for checking that it has + * requisite metadata, e.g. role="menuitem". + * + * @param menuObj + * The object that is a wrapper for the PopupMenu DOM element that + * contains the menu item DOM element. See PopupMenuAction.js + */ var FontMenuItem = function (domNode, fontMenu) { - - this.domNode = domNode; + this.domNode = domNode; this.fontMenu = fontMenu; - this.font = ''; + this.font = ''; this.keyCode = Object.freeze({ - 'TAB': 9, - 'ENTER': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + ENTER: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); }; @@ -53,13 +52,12 @@ FontMenuItem.prototype.init = function () { this.font = this.domNode.textContent.trim().toLowerCase(); - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - + this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); + this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); + this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); }; /* EVENT HANDLERS */ @@ -70,11 +68,11 @@ FontMenuItem.prototype.handleKeydown = function (event) { char = event.key, clickEvent; - function isPrintableCharacter (str) { + function isPrintableCharacter(str) { return str.length === 1 && str.match(/\S/); } - if (event.ctrlKey || event.altKey || event.metaKey) { + if (event.ctrlKey || event.altKey || event.metaKey) { return; } @@ -82,9 +80,7 @@ FontMenuItem.prototype.handleKeydown = function (event) { if (isPrintableCharacter(char)) { this.fontMenu.setFocusByFirstCharacter(this, char); } - } - else { - + } else { switch (event.keyCode) { case this.keyCode.SPACE: case this.keyCode.ENTER: @@ -164,7 +160,6 @@ FontMenuItem.prototype.handleBlur = function (event) { FontMenuItem.prototype.handleMouseover = function (event) { this.fontMenu.hasHover = true; this.fontMenu.open(); - }; FontMenuItem.prototype.handleMouseout = function (event) { diff --git a/examples/toolbar/js/FormatToolbar.js b/examples/toolbar/js/FormatToolbar.js index d9517f7c43..4b40996ee2 100644 --- a/examples/toolbar/js/FormatToolbar.js +++ b/examples/toolbar/js/FormatToolbar.js @@ -1,9 +1,9 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: FormatToolbar.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: FormatToolbar.js + */ 'use strict'; @@ -35,29 +35,29 @@ function FormatToolbar(domNode) { this.selected = null; this.nightModeCheck = null; - } FormatToolbar.prototype.init = function () { var i, items, toolbarItem, menuButton; - this.textarea = document.getElementById(this.domNode.getAttribute('aria-controls')); - this.textarea.style.width = (this.domNode.getBoundingClientRect().width - 12) + 'px'; + this.textarea = document.getElementById( + this.domNode.getAttribute('aria-controls') + ); + this.textarea.style.width = + this.domNode.getBoundingClientRect().width - 12 + 'px'; this.textarea.addEventListener('mouseup', this.selectTextContent.bind(this)); this.textarea.addEventListener('keyup', this.selectTextContent.bind(this)); this.selected = this.textarea.selectText; - this.copyButton = this.domNode.querySelector('.copy'); - this.cutButton = this.domNode.querySelector('.cut'); + this.copyButton = this.domNode.querySelector('.copy'); + this.cutButton = this.domNode.querySelector('.cut'); this.pasteButton = this.domNode.querySelector('.paste'); this.nightModeCheck = this.domNode.querySelector('.nightmode'); - items = this.domNode.querySelectorAll('.item'); - + items = this.domNode.querySelectorAll('.item'); for (i = 0; i < items.length; i++) { - toolbarItem = new FormatToolbarItem(items[i], this); toolbarItem.init(); @@ -79,26 +79,33 @@ FormatToolbar.prototype.init = function () { var s = new SpinButton(spinButtons[i], this); s.init(); } - }; FormatToolbar.prototype.selectTextContent = function () { this.start = this.textarea.selectionStart; this.end = this.textarea.selectionEnd; this.selected = this.textarea.value.substring(this.start, this.end); - this.updateDisable(this.copyButton, this.cutButton, this.pasteButton, this.selected); - + this.updateDisable( + this.copyButton, + this.cutButton, + this.pasteButton, + this.selected + ); }; -FormatToolbar.prototype.updateDisable = function (copyButton, cutButton, pasteButton, selectedContent) { +FormatToolbar.prototype.updateDisable = function ( + copyButton, + cutButton, + pasteButton, + selectedContent +) { var start = this.textarea.selectionStart; var end = this.textarea.selectionEnd; if (start !== end) { copyButton.setAttribute('aria-disabled', false); - cutButton.setAttribute('aria-disabled', false); - } - else { + cutButton.setAttribute('aria-disabled', false); + } else { copyButton.setAttribute('aria-disabled', true); - cutButton.setAttribute('aria-disabled', true); + cutButton.setAttribute('aria-disabled', true); } if (this.ourClipboard.length > 0) { pasteButton.setAttribute('aria-disabled', false); @@ -106,7 +113,7 @@ FormatToolbar.prototype.updateDisable = function (copyButton, cutButton, pasteBu }; FormatToolbar.prototype.selectText = function (start, end, textarea) { - if (typeof textarea.selectionStart !== "undefined") { + if (typeof textarea.selectionStart !== 'undefined') { textarea.focus(); textarea.selectionStart = start; textarea.selectionEnd = end; @@ -119,8 +126,12 @@ FormatToolbar.prototype.copyTextContent = function (toolbarItem) { } this.selectText(this.start, this.end, this.textarea); this.ourClipboard = this.selected; - this.updateDisable(this.copyButton, this.cutButton, this.pasteButton, this.selected); - + this.updateDisable( + this.copyButton, + this.cutButton, + this.pasteButton, + this.selected + ); }; FormatToolbar.prototype.cutTextContent = function (toolbarItem) { @@ -129,9 +140,14 @@ FormatToolbar.prototype.cutTextContent = function (toolbarItem) { } this.copyTextContent(toolbarItem); var str = this.textarea.value; - this.textarea.value = str.replace(str.substring(this.start, this.end),''); + this.textarea.value = str.replace(str.substring(this.start, this.end), ''); this.selected = ''; - this.updateDisable(this.copyButton, this.cutButton, this.pasteButton, this.selected); + this.updateDisable( + this.copyButton, + this.cutButton, + this.pasteButton, + this.selected + ); }; FormatToolbar.prototype.pasteTextContent = function () { @@ -139,18 +155,24 @@ FormatToolbar.prototype.pasteTextContent = function () { return; } var str = this.textarea.value; - this.textarea.value = str.slice(0,this.textarea.selectionStart) + this.ourClipboard + str.slice(this.textarea.selectionEnd); + this.textarea.value = + str.slice(0, this.textarea.selectionStart) + + this.ourClipboard + + str.slice(this.textarea.selectionEnd); this.textarea.focus(); - this.updateDisable(this.copyButton, this.cutButton, this.pasteButton, this.selected); + this.updateDisable( + this.copyButton, + this.cutButton, + this.pasteButton, + this.selected + ); }; - FormatToolbar.prototype.toggleBold = function (toolbarItem) { if (toolbarItem.isPressed()) { this.textarea.style.fontWeight = 'normal'; toolbarItem.resetPressed(); - } - else { + } else { this.textarea.style.fontWeight = 'bold'; toolbarItem.setPressed(); } @@ -160,8 +182,7 @@ FormatToolbar.prototype.toggleUnderline = function (toolbarItem) { if (toolbarItem.isPressed()) { this.textarea.style.textDecoration = 'none'; toolbarItem.resetPressed(); - } - else { + } else { this.textarea.style.textDecoration = 'underline'; toolbarItem.setPressed(); } @@ -171,8 +192,7 @@ FormatToolbar.prototype.toggleItalic = function (toolbarItem) { if (toolbarItem.isPressed()) { this.textarea.style.fontStyle = 'normal'; toolbarItem.resetPressed(); - } - else { + } else { this.textarea.style.fontStyle = 'italic'; toolbarItem.setPressed(); } @@ -186,18 +206,14 @@ FormatToolbar.prototype.toggleNightMode = function (toolbarItem) { if (this.nightModeCheck.checked) { this.textarea.style.color = '#eee'; this.textarea.style.background = 'black'; - } - else { + } else { this.textarea.style.color = 'black'; this.textarea.style.background = 'white'; } }; FormatToolbar.prototype.redirectLink = function (toolbarItem) { - window.open( - toolbarItem.domNode.href, - '_blank' - ); + window.open(toolbarItem.domNode.href, '_blank'); }; FormatToolbar.prototype.setAlignment = function (toolbarItem) { @@ -269,7 +285,6 @@ FormatToolbar.prototype.activateItem = function (toolbarItem) { break; default: break; - } }; @@ -294,8 +309,7 @@ FormatToolbar.prototype.setFocusToNext = function (currentItem) { if (currentItem === this.lastItem) { newItem = this.firstItem; - } - else { + } else { index = this.toolbarItems.indexOf(currentItem); newItem = this.toolbarItems[index + 1]; } @@ -307,8 +321,7 @@ FormatToolbar.prototype.setFocusToPrevious = function (currentItem) { if (currentItem === this.firstItem) { newItem = this.lastItem; - } - else { + } else { index = this.toolbarItems.indexOf(currentItem); newItem = this.toolbarItems[index - 1]; } @@ -325,20 +338,21 @@ FormatToolbar.prototype.setFocusToLast = function (currentItem) { FormatToolbar.prototype.hidePopupLabels = function () { var tps = this.domNode.querySelectorAll('button .popup-label'); - tps.forEach(function (tp) {tp.classList.remove('show');}); + tps.forEach(function (tp) { + tp.classList.remove('show'); + }); }; - // Initialize toolbars /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* ARIA Toolbar Examples -* @function onload -* @desc Initialize the toolbar example once the page has loaded -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * ARIA Toolbar Examples + * @function onload + * @desc Initialize the toolbar example once the page has loaded + */ window.addEventListener('load', function () { var toolbars = document.querySelectorAll('[role="toolbar"].format'); diff --git a/examples/toolbar/js/FormatToolbarItem.js b/examples/toolbar/js/FormatToolbarItem.js index d889974095..d50d32709d 100644 --- a/examples/toolbar/js/FormatToolbarItem.js +++ b/examples/toolbar/js/FormatToolbarItem.js @@ -1,9 +1,9 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: FontToolbarItem.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: FontToolbarItem.js + */ 'use strict'; @@ -16,20 +16,19 @@ function FormatToolbarItem(domNode, toolbar) { this.hasHover = false; this.popupLabelDelay = 800; - this.keyCode = Object.freeze({ - 'TAB': 9, - 'ENTER': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 + TAB: 9, + ENTER: 13, + ESC: 27, + SPACE: 32, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, }); } @@ -41,8 +40,10 @@ FormatToolbarItem.prototype.init = function () { this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this)); this.domNode.addEventListener('mouseleave', this.handleMouseLeave.bind(this)); - document.body.addEventListener('keydown', this.handleHideAllPopupLabels.bind(this)); - + document.body.addEventListener( + 'keydown', + this.handleHideAllPopupLabels.bind(this) + ); if (this.domNode.classList.contains('bold')) { this.buttonAction = 'bold'; @@ -97,13 +98,13 @@ FormatToolbarItem.prototype.init = function () { if (this.popupLabelNode) { var width = 8 * this.popupLabelNode.textContent.length; this.popupLabelNode.style.width = width + 'px'; - this.popupLabelNode.style.left = -1 * ((width - this.domNode.offsetWidth) / 2) - 5 + 'px'; + this.popupLabelNode.style.left = + -1 * ((width - this.domNode.offsetWidth) / 2) - 5 + 'px'; } - }; FormatToolbarItem.prototype.isPressed = function () { - return this.domNode.getAttribute('aria-pressed') === 'true'; + return this.domNode.getAttribute('aria-pressed') === 'true'; }; FormatToolbarItem.prototype.setPressed = function () { @@ -114,11 +115,9 @@ FormatToolbarItem.prototype.resetPressed = function () { this.domNode.setAttribute('aria-pressed', 'false'); }; - FormatToolbarItem.prototype.setChecked = function () { this.domNode.setAttribute('aria-checked', 'true'); this.domNode.checked = true; - }; FormatToolbarItem.prototype.resetChecked = function () { @@ -147,13 +146,10 @@ FormatToolbarItem.prototype.hidePopupLabel = function () { } }; - // Events FormatToolbarItem.prototype.handleHideAllPopupLabels = function (event) { - switch (event.keyCode) { - case this.keyCode.ESC: this.toolbar.hidePopupLabels(); break; @@ -161,11 +157,8 @@ FormatToolbarItem.prototype.handleHideAllPopupLabels = function (event) { default: break; } - - }; - FormatToolbarItem.prototype.handleBlur = function (event) { this.toolbar.domNode.classList.remove('focus'); @@ -198,13 +191,14 @@ FormatToolbarItem.prototype.handleKeyDown = function (event) { var flag = false; switch (event.keyCode) { - case this.keyCode.ENTER: case this.keyCode.SPACE: - if ((this.buttonAction !== '') && - (this.buttonAction !== 'bold') && - (this.buttonAction !== 'italic') && - (this.buttonAction !== 'underline')) { + if ( + this.buttonAction !== '' && + this.buttonAction !== 'bold' && + this.buttonAction !== 'italic' && + this.buttonAction !== 'underline' + ) { this.toolbar.activateItem(this); if (this.buttonAction !== 'nightmode') { flag = true; @@ -236,8 +230,7 @@ FormatToolbarItem.prototype.handleKeyDown = function (event) { if (this.buttonAction === 'align') { if (this.domNode.classList.contains('align-left')) { this.toolbar.setFocusToLastAlignItem(); - } - else { + } else { this.toolbar.setFocusToPrevious(this); } flag = true; @@ -247,8 +240,7 @@ FormatToolbarItem.prototype.handleKeyDown = function (event) { if (this.buttonAction === 'align') { if (this.domNode.classList.contains('align-right')) { this.toolbar.setFocusToFirstAlignItem(); - } - else { + } else { this.toolbar.setFocusToNext(this); } flag = true; @@ -262,11 +254,9 @@ FormatToolbarItem.prototype.handleKeyDown = function (event) { event.stopPropagation(); event.preventDefault(); } - }; FormatToolbarItem.prototype.handleClick = function (e) { - if (this.buttonAction == 'link') { return; } diff --git a/examples/toolbar/js/SpinButton.js b/examples/toolbar/js/SpinButton.js index 15b8105792..3fb5570d37 100644 --- a/examples/toolbar/js/SpinButton.js +++ b/examples/toolbar/js/SpinButton.js @@ -1,15 +1,14 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: SpinButton.js -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: SpinButton.js + */ 'use strict'; // Create SpinButton that contains value, valuemin, valuemax, and valuenow -var SpinButton = function (domNode, toolbar) { - +var SpinButton = function (domNode, toolbar) { this.domNode = domNode; this.toolbar = toolbar; @@ -17,47 +16,50 @@ var SpinButton = function (domNode, toolbar) { this.increaseDomNode = domNode.querySelector('.increase'); this.decreaseDomNode = domNode.querySelector('.decrease'); - this.valueMin = 8; - this.valueMax = 40; - this.valueNow = 12; + this.valueMin = 8; + this.valueMax = 40; + this.valueNow = 12; this.valueText = this.valueNow + ' Point'; this.keyCode = Object.freeze({ - 'UP': 38, - 'DOWN': 40, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36 + UP: 38, + DOWN: 40, + PAGEUP: 33, + PAGEDOWN: 34, + END: 35, + HOME: 36, }); }; // Initialize slider SpinButton.prototype.init = function () { - if (this.domNode.getAttribute('aria-valuemin')) { - this.valueMin = parseInt((this.domNode.getAttribute('aria-valuemin'))); + this.valueMin = parseInt(this.domNode.getAttribute('aria-valuemin')); } if (this.domNode.getAttribute('aria-valuemax')) { - this.valueMax = parseInt((this.domNode.getAttribute('aria-valuemax'))); + this.valueMax = parseInt(this.domNode.getAttribute('aria-valuemax')); } if (this.domNode.getAttribute('aria-valuenow')) { - this.valueNow = parseInt((this.domNode.getAttribute('aria-valuenow'))); + this.valueNow = parseInt(this.domNode.getAttribute('aria-valuenow')); } this.setValue(this.valueNow); - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - - this.increaseDomNode.addEventListener('click', this.handleIncreaseClick.bind(this)); - this.decreaseDomNode.addEventListener('click', this.handleDecreaseClick.bind(this)); + this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.increaseDomNode.addEventListener( + 'click', + this.handleIncreaseClick.bind(this) + ); + this.decreaseDomNode.addEventListener( + 'click', + this.handleDecreaseClick.bind(this) + ); }; SpinButton.prototype.setValue = function (value) { - if (value > this.valueMax) { value = this.valueMax; } @@ -66,7 +68,7 @@ SpinButton.prototype.setValue = function (value) { value = this.valueMin; } - this.valueNow = value; + this.valueNow = value; this.valueText = value + ' Point'; this.domNode.setAttribute('aria-valuenow', this.valueNow); @@ -77,11 +79,9 @@ SpinButton.prototype.setValue = function (value) { } this.toolbar.changeFontSize(value); - }; SpinButton.prototype.handleKeyDown = function (event) { - var flag = false; switch (event.keyCode) { @@ -123,23 +123,18 @@ SpinButton.prototype.handleKeyDown = function (event) { event.preventDefault(); event.stopPropagation(); } - }; SpinButton.prototype.handleIncreaseClick = function (event) { - this.setValue(this.valueNow + 1); event.preventDefault(); event.stopPropagation(); - }; SpinButton.prototype.handleDecreaseClick = function (event) { - this.setValue(this.valueNow - 1); event.preventDefault(); event.stopPropagation(); - }; diff --git a/examples/treegrid/js/treegrid-1.js b/examples/treegrid/js/treegrid-1.js index 2c14a10dc5..d7234f4803 100644 --- a/examples/treegrid/js/treegrid-1.js +++ b/examples/treegrid/js/treegrid-1.js @@ -1,7 +1,7 @@ 'use strict'; /* exported TreeGrid */ -function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { - function initAttributes () { +function TreeGrid(treegridElem, doAllowRowFocus, doStartRowFocus) { + function initAttributes() { // Make sure focusable elements are not in the tab order // They will be added back in for the active row setTabIndexOfFocusableElems(treegridElem, -1); @@ -16,8 +16,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { while (index--) { if (doAllowRowFocus) { rows[index].tabIndex = index === startRowIndex ? 0 : -1; - } - else { + } else { setTabIndexForCellsInRow(rows[index], -1); moveAriaExpandedToFirstCell(rows[index]); } @@ -32,12 +31,12 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { setTabIndexForCell(firstCell); } - function setTabIndexForCell (cell, tabIndex) { + function setTabIndexForCell(cell, tabIndex) { var focusable = getFocusableElems(cell)[0] || cell; focusable.tabIndex = tabIndex; } - function setTabIndexForCellsInRow (row, tabIndex) { + function setTabIndexForCellsInRow(row, tabIndex) { var cells = getNavigableCols(row); var cellIndex = cells.length; while (cellIndex--) { @@ -45,12 +44,12 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } } - function getAllRows () { + function getAllRows() { var nodeList = treegridElem.querySelectorAll('tbody > tr'); return Array.prototype.slice.call(nodeList); } - function getFocusableElems (root) { + function getFocusableElems(root) { // textarea not supported as a cell widget as it's multiple lines // and needs up/down keys // These should all be descendants of a cell @@ -58,7 +57,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { return Array.prototype.slice.call(nodeList); } - function setTabIndexOfFocusableElems (root, tabIndex) { + function setTabIndexOfFocusableElems(root, tabIndex) { var focusableElems = getFocusableElems(root); var index = focusableElems.length; while (index--) { @@ -66,30 +65,32 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } } - function getAllNavigableRows () { - var nodeList = treegridElem.querySelectorAll('tbody > tr:not([class~="hidden"])'); + function getAllNavigableRows() { + var nodeList = treegridElem.querySelectorAll( + 'tbody > tr:not([class~="hidden"])' + ); // Convert to array so that we can use array methods on it return Array.prototype.slice.call(nodeList); } - function getNavigableCols (currentRow) { + function getNavigableCols(currentRow) { var nodeList = currentRow.getElementsByTagName('td'); return Array.prototype.slice.call(nodeList); } - function restrictIndex (index, numItems) { + function restrictIndex(index, numItems) { if (index < 0) { return 0; } return index >= numItems ? index - 1 : index; } - function focus (elem) { + function focus(elem) { elem.tabIndex = 0; // Ensure focusable elem.focus(); } - function focusCell (cell) { + function focusCell(cell) { // Check for focusable child such as link or textbox // and use that if available var focusableChildren = getFocusableElems(cell); @@ -98,18 +99,22 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { // Restore tabIndex to what it should be when focus switches from // one treegrid item to another - function onFocusIn (event) { + function onFocusIn(event) { var newTreeGridFocus = - event.target !== window && treegridElem.contains(event.target) && - event.target; + event.target !== window && + treegridElem.contains(event.target) && + event.target; // The last row we considered focused var oldCurrentRow = enableTabbingInActiveRowDescendants.tabbingRow; if (oldCurrentRow) { enableTabbingInActiveRowDescendants(false, oldCurrentRow); } - if (doAllowRowFocus && onFocusIn.prevTreeGridFocus && - onFocusIn.prevTreeGridFocus.localName === 'td') { + if ( + doAllowRowFocus && + onFocusIn.prevTreeGridFocus && + onFocusIn.prevTreeGridFocus.localName === 'td' + ) { // Was focused on td, remove tabIndex so that it's not focused on click onFocusIn.prevTreeGridFocus.removeAttribute('tabindex'); } @@ -135,13 +140,12 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } // Set whether interactive elements within a row are tabbable - function enableTabbingInActiveRowDescendants (isTabbingOn, row) { + function enableTabbingInActiveRowDescendants(isTabbingOn, row) { if (row) { setTabIndexOfFocusableElems(row, isTabbingOn ? 0 : -1); if (isTabbingOn) { enableTabbingInActiveRowDescendants.tabbingRow = row; - } - else { + } else { if (enableTabbingInActiveRowDescendants.tabbingRow === row) { enableTabbingInActiveRowDescendants.tabbingRow = null; } @@ -151,11 +155,11 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { // The row with focus is the row that either has focus or an element // inside of it has focus - function getRowWithFocus () { + function getRowWithFocus() { return getContainingRow(document.activeElement); } - function getContainingRow (start) { + function getContainingRow(start) { var possibleRow = start; if (treegridElem.contains(possibleRow)) { while (possibleRow !== treegridElem) { @@ -167,17 +171,17 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } } - function isRowFocused () { + function isRowFocused() { return getRowWithFocus() === document.activeElement; } // Note: contenteditable not currently supported - function isEditableFocused () { + function isEditableFocused() { var focusedElem = document.activeElement; return focusedElem.localName === 'input'; } - function getColWithFocus (currentRow) { + function getColWithFocus(currentRow) { if (currentRow) { var possibleCol = document.activeElement; if (currentRow.contains(possibleCol)) { @@ -191,17 +195,17 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } } - function getLevel (row) { + function getLevel(row) { return row && parseInt(row.getAttribute('aria-level')); } // Move backwards (direction = -1) or forwards (direction = 1) // If we also need to move down/up a level, requireLevelChange = true // When - function moveByRow (direction, requireLevelChange) { + function moveByRow(direction, requireLevelChange) { var currentRow = getRowWithFocus(); - var requiredLevel = requireLevelChange && currentRow && - getLevel(currentRow) + direction; + var requiredLevel = + requireLevelChange && currentRow && getLevel(currentRow) + direction; var rows = getAllNavigableRows(); var numRows = rows.length; var rowIndex = currentRow ? rows.indexOf(currentRow) : -1; @@ -215,15 +219,14 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { return; // Failed to find required level, return without focus change } rowIndex = restrictIndex(rowIndex + direction, numRows); - } - while (requiredLevel && requiredLevel !== getLevel(rows[rowIndex])); + } while (requiredLevel && requiredLevel !== getLevel(rows[rowIndex])); if (!focusSameColInDifferentRow(currentRow, rows[rowIndex])) { focus(rows[rowIndex]); } } - function focusSameColInDifferentRow (fromRow, toRow) { + function focusSameColInDifferentRow(fromRow, toRow) { var currentCol = getColWithFocus(fromRow); if (!currentCol) { return; @@ -242,7 +245,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { return true; } - function moveToExtreme (direction) { + function moveToExtreme(direction) { var currentRow = getRowWithFocus(); if (!currentRow) { return; @@ -250,14 +253,13 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { var currentCol = getColWithFocus(currentRow); if (currentCol) { moveToExtremeCol(direction, currentRow); - } - else { + } else { // Move to first/last row moveToExtremeRow(direction); } } - function moveByCol (direction) { + function moveByCol(direction) { var currentRow = getRowWithFocus(); if (!currentRow) { return; @@ -267,8 +269,8 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { var currentCol = getColWithFocus(currentRow); var currentColIndex = cols.indexOf(currentCol); // First right arrow moves to first column - var newColIndex = (currentCol || direction < 0) ? currentColIndex + - direction : 0; + var newColIndex = + currentCol || direction < 0 ? currentColIndex + direction : 0; // Moving past beginning focuses row if (doAllowRowFocus && newColIndex < 0) { focus(currentRow); @@ -278,14 +280,14 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { focusCell(cols[newColIndex]); } - function moveToExtremeCol (direction, currentRow) { + function moveToExtremeCol(direction, currentRow) { // Move to first/last col var cols = getNavigableCols(currentRow); var desiredColIndex = direction < 0 ? 0 : cols.length - 1; focusCell(cols[desiredColIndex]); } - function moveToExtremeRow (direction) { + function moveToExtremeRow(direction) { var rows = getAllNavigableRows(); var newRow = rows[direction > 0 ? rows.length - 1 : 0]; if (!focusSameColInDifferentRow(getRowWithFocus(), newRow)) { @@ -293,7 +295,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } } - function doPrimaryAction () { + function doPrimaryAction() { var currentRow = getRowWithFocus(); if (!currentRow) { return; @@ -301,8 +303,12 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { // If row has focus, open message if (currentRow === document.activeElement) { - alert('Message from ' + currentRow.children[2].innerText + ':\n\n' + - currentRow.children[1].innerText); + alert( + 'Message from ' + + currentRow.children[2].innerText + + ':\n\n' + + currentRow.children[1].innerText + ); return; } @@ -310,7 +316,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { toggleExpanded(currentRow); } - function toggleExpanded (row) { + function toggleExpanded(row) { var cols = getNavigableCols(row); var currentCol = getColWithFocus(row); if (currentCol === cols[0] && isExpandable(row)) { @@ -318,7 +324,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } } - function changeExpanded (doExpand, row) { + function changeExpanded(doExpand, row) { var currentRow = row || getRowWithFocus(); if (!currentRow) { return; @@ -339,16 +345,14 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { // Only expand the next level if this level is expanded // and previous level is expanded doExpandLevel[rowLevel + 1] = - doExpandLevel[rowLevel] && - isExpanded(nextRow); + doExpandLevel[rowLevel] && isExpanded(nextRow); var willHideRow = !doExpandLevel[rowLevel]; var isRowHidden = nextRow.classList.contains('hidden'); if (willHideRow !== isRowHidden) { if (willHideRow) { nextRow.classList.add('hidden'); - } - else { + } else { nextRow.classList.remove('hidden'); } didChange = true; @@ -363,7 +367,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { // Mirror aria-expanded from the row to the first cell in that row // (TBD is this a good idea? How else will screen reader user hear // that the cell represents the opportunity to collapse/expand rows?) - function moveAriaExpandedToFirstCell (row) { + function moveAriaExpandedToFirstCell(row) { var expandedValue = row.getAttribute('aria-expanded'); var firstCell = getNavigableCols(row)[0]; if (expandedValue) { @@ -372,26 +376,26 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } } - function getAriaExpandedElem (row) { + function getAriaExpandedElem(row) { return doAllowRowFocus ? row : getNavigableCols(row)[0]; } - function setAriaExpanded (row, doExpand) { + function setAriaExpanded(row, doExpand) { var elem = getAriaExpandedElem(row); elem.setAttribute('aria-expanded', doExpand); } - function isExpandable (row) { + function isExpandable(row) { var elem = getAriaExpandedElem(row); return elem.hasAttribute('aria-expanded'); } - function isExpanded (row) { + function isExpanded(row) { var elem = getAriaExpandedElem(row); return elem.getAttribute('aria-expanded') === 'true'; } - function onKeyDown (event) { + function onKeyDown(event) { var ENTER = 13; var UP = 38; var DOWN = 40; @@ -402,15 +406,14 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { var CTRL_HOME = -HOME; var CTRL_END = -END; - var numModifiersPressed = event.ctrlKey + event.altKey + event.shiftKey + - event.metaKey; + var numModifiersPressed = + event.ctrlKey + event.altKey + event.shiftKey + event.metaKey; var key = event.keyCode; if (numModifiersPressed === 1 && event.ctrlKey) { key = -key; // Treat as negative key value when ctrl pressed - } - else if (numModifiersPressed) { + } else if (numModifiersPressed) { return; } @@ -423,18 +426,17 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { break; case LEFT: if (isEditableFocused()) { - return; // Leave key for editable area + return; // Leave key for editable area } if (isRowFocused()) { changeExpanded(false) || moveByRow(-1, true); - } - else { + } else { moveByCol(-1); } break; case RIGHT: if (isEditableFocused()) { - return; // Leave key for editable area + return; // Leave key for editable area } // If row: try to expand @@ -448,7 +450,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { break; case HOME: if (isEditableFocused()) { - return; // Leave key for editable area + return; // Leave key for editable area } moveToExtreme(-1); break; @@ -457,7 +459,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { break; case END: if (isEditableFocused()) { - return; // Leave key for editable area + return; // Leave key for editable area } moveToExtreme(1); break; @@ -475,7 +477,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { // Toggle row expansion if the click is over the expando triangle // Since the triangle is a pseudo element we can't bind an event listener // to it. Another option is to have an actual element with role="presentation" - function onClick (event) { + function onClick(event) { var target = event.target; if (target.localName !== 'td') { return; @@ -498,7 +500,7 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { } // Double click on row toggles expansion - function onDoubleClick (event) { + function onDoubleClick(event) { var row = getContainingRow(event.target); if (row) { if (isExpandable(row)) { @@ -513,7 +515,9 @@ function TreeGrid (treegridElem, doAllowRowFocus, doStartRowFocus) { treegridElem.addEventListener('click', onClick); treegridElem.addEventListener('dblclick', onDoubleClick); // Polyfill for focusin necessary for Firefox < 52 - window.addEventListener(window.onfocusin ? 'focusin' : 'focus', - onFocusIn, true); + window.addEventListener( + window.onfocusin ? 'focusin' : 'focus', + onFocusIn, + true + ); } - diff --git a/examples/treeview/treeview-1/js/tree.js b/examples/treeview/treeview-1/js/tree.js index 426d82a4fb..e55e824188 100644 --- a/examples/treeview/treeview-1/js/tree.js +++ b/examples/treeview/treeview-1/js/tree.js @@ -1,12 +1,12 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: Tree.js -* -* Desc: Tree widget that implements ARIA Authoring Practices -* for a tree being used as a file viewer -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: Tree.js + * + * Desc: Tree widget that implements ARIA Authoring Practices + * for a tree being used as a file viewer + */ 'use strict'; @@ -17,26 +17,24 @@ */ window.addEventListener('load', function () { - var trees = document.querySelectorAll('[role="tree"]'); for (var i = 0; i < trees.length; i++) { var t = new Tree(trees[i]); t.init(); } - }); /* -* @constructor -* -* @desc -* Tree item object for representing the state and user interactions for a -* tree widget -* -* @param node -* An element with the role=tree attribute -*/ + * @constructor + * + * @desc + * Tree item object for representing the state and user interactions for a + * tree widget + * + * @param node + * An element with the role=tree attribute + */ var Tree = function (node) { // Check whether node is a DOM element @@ -51,18 +49,14 @@ var Tree = function (node) { this.firstTreeitem = null; this.lastTreeitem = null; - }; Tree.prototype.init = function () { - - function findTreeitems (node, tree, group) { - + function findTreeitems(node, tree, group) { var elem = node.firstElementChild; var ti = group; while (elem) { - if (elem.tagName.toLowerCase() === 'li') { ti = new Treeitem(elem, tree, group); ti.init(); @@ -88,30 +82,25 @@ Tree.prototype.init = function () { this.updateVisibleTreeitems(); this.firstTreeitem.domNode.tabIndex = 0; - }; Tree.prototype.setFocusToItem = function (treeitem) { - for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; if (ti === treeitem) { ti.domNode.tabIndex = 0; ti.domNode.focus(); - } - else { + } else { ti.domNode.tabIndex = -1; } } - }; Tree.prototype.setFocusToNextItem = function (currentItem) { - var nextItem = false; - for (var i = (this.treeitems.length - 1); i >= 0; i--) { + for (var i = this.treeitems.length - 1; i >= 0; i--) { var ti = this.treeitems[i]; if (ti === currentItem) { break; @@ -124,11 +113,9 @@ Tree.prototype.setFocusToNextItem = function (currentItem) { if (nextItem) { this.setFocusToItem(nextItem); } - }; Tree.prototype.setFocusToPreviousItem = function (currentItem) { - var prevItem = false; for (var i = 0; i < this.treeitems.length; i++) { @@ -147,7 +134,6 @@ Tree.prototype.setFocusToPreviousItem = function (currentItem) { }; Tree.prototype.setFocusToParentItem = function (currentItem) { - if (currentItem.groupTreeitem) { this.setFocusToItem(currentItem.groupTreeitem); } @@ -162,33 +148,28 @@ Tree.prototype.setFocusToLastItem = function () { }; Tree.prototype.expandTreeitem = function (currentItem) { - if (currentItem.isExpandable) { currentItem.domNode.setAttribute('aria-expanded', true); this.updateVisibleTreeitems(); } - }; Tree.prototype.expandAllSiblingItems = function (currentItem) { for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; - if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) { + if (ti.groupTreeitem === currentItem.groupTreeitem && ti.isExpandable) { this.expandTreeitem(ti); } } - }; Tree.prototype.collapseTreeitem = function (currentItem) { - var groupTreeitem = false; if (currentItem.isExpanded()) { groupTreeitem = currentItem; - } - else { + } else { groupTreeitem = currentItem.groupTreeitem; } @@ -197,11 +178,9 @@ Tree.prototype.collapseTreeitem = function (currentItem) { this.updateVisibleTreeitems(); this.setFocusToItem(groupTreeitem); } - }; Tree.prototype.updateVisibleTreeitems = function () { - this.firstTreeitem = this.treeitems[0]; for (var i = 0; i < this.treeitems.length; i++) { @@ -211,8 +190,7 @@ Tree.prototype.updateVisibleTreeitems = function () { ti.isVisible = true; - while (parent && (parent !== this.domNode)) { - + while (parent && parent !== this.domNode) { if (parent.getAttribute('aria-expanded') == 'false') { ti.isVisible = false; } @@ -223,7 +201,6 @@ Tree.prototype.updateVisibleTreeitems = function () { this.lastTreeitem = ti; } } - }; Tree.prototype.setFocusByFirstCharacter = function (currentItem, char) { diff --git a/examples/treeview/treeview-1/js/treeitem.js b/examples/treeview/treeview-1/js/treeitem.js index bf2e1a152b..84d31d3b74 100644 --- a/examples/treeview/treeview-1/js/treeitem.js +++ b/examples/treeview/treeview-1/js/treeitem.js @@ -1,28 +1,27 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: Treeitem.js -* -* Desc: Treeitem widget that implements ARIA Authoring Practices -* for a tree being used as a file viewer -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: Treeitem.js + * + * Desc: Treeitem widget that implements ARIA Authoring Practices + * for a tree being used as a file viewer + */ 'use strict'; /* -* @constructor -* -* @desc -* Treeitem object for representing the state and user interactions for a -* treeItem widget -* -* @param node -* An element with the role=tree attribute -*/ + * @constructor + * + * @desc + * Treeitem object for representing the state and user interactions for a + * treeItem widget + * + * @param node + * An element with the role=tree attribute + */ var Treeitem = function (node, treeObj, group) { - // Check whether node is a DOM element if (typeof node !== 'object') { return; @@ -49,7 +48,6 @@ var Treeitem = function (node, treeObj, group) { var elem = node.firstElementChild; while (elem) { - if (elem.tagName.toLowerCase() == 'ul') { elem.setAttribute('role', 'group'); this.isExpandable = true; @@ -69,7 +67,7 @@ var Treeitem = function (node, treeObj, group) { LEFT: 37, UP: 38, RIGHT: 39, - DOWN: 40 + DOWN: 40, }); }; @@ -92,34 +90,30 @@ Treeitem.prototype.init = function () { }; Treeitem.prototype.isExpanded = function () { - if (this.isExpandable) { return this.domNode.getAttribute('aria-expanded') === 'true'; } return false; - }; /* EVENT HANDLERS */ Treeitem.prototype.handleKeydown = function (event) { - var tgt = event.currentTarget, flag = false, char = event.key, clickEvent; - function isPrintableCharacter (str) { + function isPrintableCharacter(str) { return str.length === 1 && str.match(/\S/); } - function printableCharacter (item) { + function printableCharacter(item) { if (char == '*') { item.tree.expandAllSiblingItems(item); flag = true; - } - else { + } else { if (isPrintableCharacter(char)) { item.tree.setFocusByFirstCharacter(item, char); flag = true; @@ -135,8 +129,7 @@ Treeitem.prototype.handleKeydown = function (event) { if (isPrintableCharacter(char)) { printableCharacter(this); } - } - else { + } else { switch (event.keyCode) { case this.keyCode.SPACE: case this.keyCode.RETURN: @@ -144,12 +137,11 @@ Treeitem.prototype.handleKeydown = function (event) { // and let the event handler handleClick do the housekeeping. try { clickEvent = new MouseEvent('click', { - 'view': window, - 'bubbles': true, - 'cancelable': true + view: window, + bubbles: true, + cancelable: true, }); - } - catch (err) { + } catch (err) { if (document.createEvent) { // DOM Level 3 for IE 9+ clickEvent = document.createEvent('MouseEvents'); @@ -174,8 +166,7 @@ Treeitem.prototype.handleKeydown = function (event) { if (this.isExpandable) { if (this.isExpanded()) { this.tree.setFocusToNextItem(this); - } - else { + } else { this.tree.expandTreeitem(this); } } @@ -186,8 +177,7 @@ Treeitem.prototype.handleKeydown = function (event) { if (this.isExpandable && this.isExpanded()) { this.tree.collapseTreeitem(this); flag = true; - } - else { + } else { if (this.inGroup) { this.tree.setFocusToParentItem(this); flag = true; @@ -211,7 +201,6 @@ Treeitem.prototype.handleKeydown = function (event) { } break; } - } if (flag) { @@ -224,13 +213,11 @@ Treeitem.prototype.handleClick = function (event) { if (this.isExpandable) { if (this.isExpanded()) { this.tree.collapseTreeitem(this); - } - else { + } else { this.tree.expandTreeitem(this); } event.stopPropagation(); - } - else { + } else { this.tree.setFocusToItem(this); } }; diff --git a/examples/treeview/treeview-1/js/treeitemClick.js b/examples/treeview/treeview-1/js/treeitemClick.js index b967753c63..f2a0d922f8 100644 --- a/examples/treeview/treeview-1/js/treeitemClick.js +++ b/examples/treeview/treeview-1/js/treeitemClick.js @@ -1,11 +1,11 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: Treeitem.js -* -* Desc: Setup click events for Tree widget examples -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: Treeitem.js + * + * Desc: Setup click events for Tree widget examples + */ 'use strict'; @@ -16,11 +16,9 @@ */ window.addEventListener('load', function () { - var treeitems = document.querySelectorAll('[role="treeitem"]'); for (var i = 0; i < treeitems.length; i++) { - treeitems[i].addEventListener('click', function (event) { var treeitem = event.currentTarget; var label = treeitem.getAttribute('aria-label'); @@ -34,7 +32,5 @@ window.addEventListener('load', function () { event.stopPropagation(); event.preventDefault(); }); - } - }); diff --git a/examples/treeview/treeview-2/js/treeLinks.js b/examples/treeview/treeview-2/js/treeLinks.js index 49c15d9262..04aac8e40d 100644 --- a/examples/treeview/treeview-2/js/treeLinks.js +++ b/examples/treeview/treeview-2/js/treeLinks.js @@ -1,12 +1,12 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: TreeLinks.js -* -* Desc: Tree widget that implements ARIA Authoring Practices -* for a tree being used as a file viewer -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: TreeLinks.js + * + * Desc: Tree widget that implements ARIA Authoring Practices + * for a tree being used as a file viewer + */ 'use strict'; @@ -17,26 +17,24 @@ */ window.addEventListener('load', function () { - var trees = document.querySelectorAll('[role="tree"]'); for (var i = 0; i < trees.length; i++) { var t = new TreeLinks(trees[i]); t.init(); } - }); /* -* @constructor -* -* @desc -* Tree item object for representing the state and user interactions for a -* tree widget -* -* @param node -* An element with the role=tree attribute -*/ + * @constructor + * + * @desc + * Tree item object for representing the state and user interactions for a + * tree widget + * + * @param node + * An element with the role=tree attribute + */ var TreeLinks = function (node) { // Check whether node is a DOM element @@ -51,19 +49,19 @@ var TreeLinks = function (node) { this.firstTreeitem = null; this.lastTreeitem = null; - }; TreeLinks.prototype.init = function () { - - function findTreeitems (node, tree, group) { - + function findTreeitems(node, tree, group) { var elem = node.firstElementChild; var ti = group; while (elem) { - - if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') { + if ( + (elem.tagName.toLowerCase() === 'li' && + elem.firstElementChild.tagName.toLowerCase() === 'span') || + elem.tagName.toLowerCase() === 'a' + ) { ti = new TreeitemLink(elem, tree, group); ti.init(); tree.treeitems.push(ti); @@ -88,30 +86,25 @@ TreeLinks.prototype.init = function () { this.updateVisibleTreeitems(); this.firstTreeitem.domNode.tabIndex = 0; - }; TreeLinks.prototype.setFocusToItem = function (treeitem) { - for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; if (ti === treeitem) { ti.domNode.tabIndex = 0; ti.domNode.focus(); - } - else { + } else { ti.domNode.tabIndex = -1; } } - }; TreeLinks.prototype.setFocusToNextItem = function (currentItem) { - var nextItem = false; - for (var i = (this.treeitems.length - 1); i >= 0; i--) { + for (var i = this.treeitems.length - 1; i >= 0; i--) { var ti = this.treeitems[i]; if (ti === currentItem) { break; @@ -124,11 +117,9 @@ TreeLinks.prototype.setFocusToNextItem = function (currentItem) { if (nextItem) { this.setFocusToItem(nextItem); } - }; TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) { - var prevItem = false; for (var i = 0; i < this.treeitems.length; i++) { @@ -147,7 +138,6 @@ TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) { }; TreeLinks.prototype.setFocusToParentItem = function (currentItem) { - if (currentItem.groupTreeitem) { this.setFocusToItem(currentItem.groupTreeitem); } @@ -162,33 +152,28 @@ TreeLinks.prototype.setFocusToLastItem = function () { }; TreeLinks.prototype.expandTreeitem = function (currentItem) { - if (currentItem.isExpandable) { currentItem.domNode.setAttribute('aria-expanded', true); this.updateVisibleTreeitems(); } - }; TreeLinks.prototype.expandAllSiblingItems = function (currentItem) { for (var i = 0; i < this.treeitems.length; i++) { var ti = this.treeitems[i]; - if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) { + if (ti.groupTreeitem === currentItem.groupTreeitem && ti.isExpandable) { this.expandTreeitem(ti); } } - }; TreeLinks.prototype.collapseTreeitem = function (currentItem) { - var groupTreeitem = false; if (currentItem.isExpanded()) { groupTreeitem = currentItem; - } - else { + } else { groupTreeitem = currentItem.groupTreeitem; } @@ -197,11 +182,9 @@ TreeLinks.prototype.collapseTreeitem = function (currentItem) { this.updateVisibleTreeitems(); this.setFocusToItem(groupTreeitem); } - }; TreeLinks.prototype.updateVisibleTreeitems = function () { - this.firstTreeitem = this.treeitems[0]; for (var i = 0; i < this.treeitems.length; i++) { @@ -211,8 +194,7 @@ TreeLinks.prototype.updateVisibleTreeitems = function () { ti.isVisible = true; - while (parent && (parent !== this.domNode)) { - + while (parent && parent !== this.domNode) { if (parent.getAttribute('aria-expanded') == 'false') { ti.isVisible = false; } @@ -223,7 +205,6 @@ TreeLinks.prototype.updateVisibleTreeitems = function () { this.lastTreeitem = ti; } } - }; TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, char) { diff --git a/examples/treeview/treeview-2/js/treeitemLinks.js b/examples/treeview/treeview-2/js/treeitemLinks.js index 4a8ea7bc77..674f0adcd1 100644 --- a/examples/treeview/treeview-2/js/treeitemLinks.js +++ b/examples/treeview/treeview-2/js/treeitemLinks.js @@ -1,28 +1,27 @@ /* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -* -* File: TreeitemLink.js -* -* Desc: Treeitem widget that implements ARIA Authoring Practices -* for a tree being used as a file viewer -*/ + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: TreeitemLink.js + * + * Desc: Treeitem widget that implements ARIA Authoring Practices + * for a tree being used as a file viewer + */ 'use strict'; /* -* @constructor -* -* @desc -* Treeitem object for representing the state and user interactions for a -* treeItem widget -* -* @param node -* An element with the role=tree attribute -*/ + * @constructor + * + * @desc + * Treeitem object for representing the state and user interactions for a + * treeItem widget + * + * @param node + * An element with the role=tree attribute + */ var TreeitemLink = function (node, treeObj, group) { - // Check whether node is a DOM element if (typeof node !== 'object') { return; @@ -50,7 +49,6 @@ var TreeitemLink = function (node, treeObj, group) { var elem = node.firstElementChild; while (elem) { - if (elem.tagName.toLowerCase() == 'ul') { elem.setAttribute('role', 'group'); this.isExpandable = true; @@ -70,7 +68,7 @@ var TreeitemLink = function (node, treeObj, group) { LEFT: 37, UP: 38, RIGHT: 39, - DOWN: 40 + DOWN: 40, }); }; @@ -87,23 +85,26 @@ TreeitemLink.prototype.init = function () { this.domNode.addEventListener('blur', this.handleBlur.bind(this)); if (this.isExpandable) { - this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this)); - this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this)); - } - else { + this.domNode.firstElementChild.addEventListener( + 'mouseover', + this.handleMouseOver.bind(this) + ); + this.domNode.firstElementChild.addEventListener( + 'mouseout', + this.handleMouseOut.bind(this) + ); + } else { this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this)); this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this)); } }; TreeitemLink.prototype.isExpanded = function () { - if (this.isExpandable) { return this.domNode.getAttribute('aria-expanded') === 'true'; } return false; - }; /* EVENT HANDLERS */ @@ -114,16 +115,15 @@ TreeitemLink.prototype.handleKeydown = function (event) { char = event.key, clickEvent; - function isPrintableCharacter (str) { + function isPrintableCharacter(str) { return str.length === 1 && str.match(/\S/); } - function printableCharacter (item) { + function printableCharacter(item) { if (char == '*') { item.tree.expandAllSiblingItems(item); flag = true; - } - else { + } else { if (isPrintableCharacter(char)) { item.tree.setFocusByFirstCharacter(item, char); flag = true; @@ -138,30 +138,29 @@ TreeitemLink.prototype.handleKeydown = function (event) { } if (event.shift) { - if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) { + if ( + event.keyCode == this.keyCode.SPACE || + event.keyCode == this.keyCode.RETURN + ) { event.stopPropagation(); this.stopDefaultClick = true; - } - else { + } else { if (isPrintableCharacter(char)) { printableCharacter(this); } } - } - else { + } else { switch (event.keyCode) { case this.keyCode.SPACE: case this.keyCode.RETURN: if (this.isExpandable) { if (this.isExpanded()) { this.tree.collapseTreeitem(this); - } - else { + } else { this.tree.expandTreeitem(this); } flag = true; - } - else { + } else { event.stopPropagation(); this.stopDefaultClick = true; } @@ -181,8 +180,7 @@ TreeitemLink.prototype.handleKeydown = function (event) { if (this.isExpandable) { if (this.isExpanded()) { this.tree.setFocusToNextItem(this); - } - else { + } else { this.tree.expandTreeitem(this); } } @@ -193,8 +191,7 @@ TreeitemLink.prototype.handleKeydown = function (event) { if (this.isExpandable && this.isExpanded()) { this.tree.collapseTreeitem(this); flag = true; - } - else { + } else { if (this.inGroup) { this.tree.setFocusToParentItem(this); flag = true; @@ -227,17 +224,18 @@ TreeitemLink.prototype.handleKeydown = function (event) { }; TreeitemLink.prototype.handleClick = function (event) { - // only process click events that directly happened on this treeitem - if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) { + if ( + event.target !== this.domNode && + event.target !== this.domNode.firstElementChild + ) { return; } if (this.isExpandable) { if (this.isExpanded()) { this.tree.collapseTreeitem(this); - } - else { + } else { this.tree.expandTreeitem(this); } event.stopPropagation(); diff --git a/package-lock.json b/package-lock.json index 41c6ed9283..ac9c54725a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2331,6 +2331,32 @@ } } }, + "eslint-config-prettier": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz", + "integrity": "sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + }, + "dependencies": { + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", + "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", diff --git a/package.json b/package.json index 132a35cb3c..d8ba5cf0e0 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "cheerio": "^1.0.0-rc.2", "cspell": "^4.1.0", "eslint": "^7.10.0", + "eslint-config-prettier": "^6.12.0", + "eslint-plugin-prettier": "^3.1.4", "geckodriver": "^1.20.0", "glob": "^7.1.6", "husky": "^4.3.0", diff --git a/respec-config.js b/respec-config.js index 8298dff3fc..72255ba293 100644 --- a/respec-config.js +++ b/respec-config.js @@ -24,62 +24,72 @@ var respecConfig = { // previousDiffURI: "", // Github repo - github: "w3c/aria-practices", + github: 'w3c/aria-practices', // If this is a LCWD, uncomment and set the end of its review period // lcEnd: "2012-02-21", // Editors, add as many as you like. // “name” is the only required field. - editors: [{ - name: 'Matt King', - mailto: 'mck@fb.com', - company: 'Facebook', - companyURI: 'https://www.facebook.com/', - w3cid: 44582 - }, { - name: 'JaEun Jemma Ku', - mailto: 'jku@illinois.edu', - company: 'University of Illinois', - companyURI: 'https://illinois.edu/', - w3cid: 74097 - }, { - name: 'James Nurthen', - mailto: 'nurthen@adobe.com', - company: 'Adobe', - companyURI: 'https://www.adobe.com/', - w3cid: 37155 - }, { - name: 'Zoë Bijl', - company: 'Invited Expert', - w3cid: 74040 - }, { - name: 'Michael Cooper', - url: 'https://www.w3.org/People/cooper/', - mailto: 'cooper@w3.org', - company: 'W3C', - companyURI: 'https://www.w3.org/', - w3cid: 34017 - }], - formerEditors: [{ - name: 'Joseph Scheuhammer', - company: 'Inclusive Design Research Centre, OCAD University', - companyURI: 'https://idrc.ocad.ca/', - w3cid: 42279, - retiredDate: '2014-10-01' - }, { - name: 'Lisa Pappas', - company: 'SAS', - companyURI: 'https://www.sas.com/', - w3cid: 41725, - retiredDate: '2009-10-01' - }, { - name: 'Rich Schwerdtfeger', - company: 'IBM Corporation', - companyURI: 'https://ibm.com/', - w3cid: 2460, - retiredDate: '2014-10-01' - }], + editors: [ + { + name: 'Matt King', + mailto: 'mck@fb.com', + company: 'Facebook', + companyURI: 'https://www.facebook.com/', + w3cid: 44582, + }, + { + name: 'JaEun Jemma Ku', + mailto: 'jku@illinois.edu', + company: 'University of Illinois', + companyURI: 'https://illinois.edu/', + w3cid: 74097, + }, + { + name: 'James Nurthen', + mailto: 'nurthen@adobe.com', + company: 'Adobe', + companyURI: 'https://www.adobe.com/', + w3cid: 37155, + }, + { + name: 'Zoë Bijl', + company: 'Invited Expert', + w3cid: 74040, + }, + { + name: 'Michael Cooper', + url: 'https://www.w3.org/People/cooper/', + mailto: 'cooper@w3.org', + company: 'W3C', + companyURI: 'https://www.w3.org/', + w3cid: 34017, + }, + ], + formerEditors: [ + { + name: 'Joseph Scheuhammer', + company: 'Inclusive Design Research Centre, OCAD University', + companyURI: 'https://idrc.ocad.ca/', + w3cid: 42279, + retiredDate: '2014-10-01', + }, + { + name: 'Lisa Pappas', + company: 'SAS', + companyURI: 'https://www.sas.com/', + w3cid: 41725, + retiredDate: '2009-10-01', + }, + { + name: 'Rich Schwerdtfeger', + company: 'IBM Corporation', + companyURI: 'https://ibm.com/', + w3cid: 2460, + retiredDate: '2014-10-01', + }, + ], // Authors, add as many as you like. // This is optional, uncomment if you have authors as well as editors. @@ -96,28 +106,28 @@ var respecConfig = { // Spec URLs ariaSpecURLs: { - 'ED': 'https://w3c.github.io/aria/', - 'FPWD': 'https://www.w3.org/TR/wai-aria-1.2/', - 'WD': 'https://www.w3.org/TR/wai-aria-1.2/', - 'REC': 'https://www.w3.org/TR/wai-aria/' + ED: 'https://w3c.github.io/aria/', + FPWD: 'https://www.w3.org/TR/wai-aria-1.2/', + WD: 'https://www.w3.org/TR/wai-aria-1.2/', + REC: 'https://www.w3.org/TR/wai-aria/', }, accNameURLs: { - 'ED': 'https://w3c.github.io/accname/', - 'WD': 'https://www.w3.org/TR/accname-1.2/', - 'FPWD': 'https://www.w3.org/TR/accname-1.2/', - 'REC': 'https://www.w3.org/TR/accname/' + ED: 'https://w3c.github.io/accname/', + WD: 'https://www.w3.org/TR/accname-1.2/', + FPWD: 'https://www.w3.org/TR/accname-1.2/', + REC: 'https://www.w3.org/TR/accname/', }, coreMappingURLs: { - 'ED': 'https://w3c.github.io/core-aam/', - 'WD': 'https://www.w3.org/TR/core-aam-1.2/', - 'FPWD': 'https://www.w3.org/TR/core-aam-1.2/', - 'REC': 'https://www.w3.org/TR/core-aam/' + ED: 'https://w3c.github.io/core-aam/', + WD: 'https://www.w3.org/TR/core-aam-1.2/', + FPWD: 'https://www.w3.org/TR/core-aam-1.2/', + REC: 'https://www.w3.org/TR/core-aam/', }, htmlMappingURLs: { - 'ED': 'https://w3c.github.io/html-aam/', - 'WD': 'https://www.w3.org/TR/html-aam-1.0/', - 'FPWD': 'https://www.w3.org/TR/html-aam-1.0/', - 'REC': 'https://www.w3.org/TR/html-aam-1.0/' + ED: 'https://w3c.github.io/html-aam/', + WD: 'https://www.w3.org/TR/html-aam-1.0/', + FPWD: 'https://www.w3.org/TR/html-aam-1.0/', + REC: 'https://www.w3.org/TR/html-aam-1.0/', }, // alternateFormats: [ @@ -154,5 +164,5 @@ var respecConfig = { // If in doubt ask your friendly neighbourhood Team Contact. wgPatentURI: 'https://www.w3.org/2004/01/pp-impl/83726/status', maxTocLevel: 4, - preProcess: [ linkCrossReferences ] + preProcess: [linkCrossReferences], }; diff --git a/scripts/coverage-report.js b/scripts/coverage-report.js index 15d534a366..af07bc1c9b 100644 --- a/scripts/coverage-report.js +++ b/scripts/coverage-report.js @@ -16,9 +16,18 @@ const cheerio = require('cheerio'); const exampleFilePath = path.join(__dirname, '..', 'coverage', 'index.html'); const exampleTemplatePath = path.join(__dirname, 'coverage-report.template'); -const csvRoleFilePath = path.join(__dirname, '..', 'coverage', 'role-coverage.csv'); -const csvPropFilePath = path.join(__dirname, '..', 'coverage', 'prop-coverage.csv'); - +const csvRoleFilePath = path.join( + __dirname, + '..', + 'coverage', + 'role-coverage.csv' +); +const csvPropFilePath = path.join( + __dirname, + '..', + 'coverage', + 'prop-coverage.csv' +); let output = fs.readFileSync(exampleTemplatePath, function (err) { console.log('Error reading aria index:', err); @@ -26,7 +35,6 @@ let output = fs.readFileSync(exampleTemplatePath, function (err) { const $ = cheerio.load(output); - const ariaRoles = [ 'alert', 'alertdialog', @@ -96,7 +104,7 @@ const ariaRoles = [ 'tooltip', 'tree', 'treegrid', - 'treeitem' + 'treeitem', ]; const ariaPropertiesAndStates = [ @@ -147,20 +155,28 @@ const ariaPropertiesAndStates = [ 'aria-valuemax', 'aria-valuemin', 'aria-valuenow', - 'aria-valuetext' + 'aria-valuetext', ]; let indexOfRolesInExamples = {}; -ariaRoles.forEach(function(role) { indexOfRolesInExamples[role] = []}); +ariaRoles.forEach(function (role) { + indexOfRolesInExamples[role] = []; +}); let indexOfRolesInGuidance = {}; -ariaRoles.forEach(function(role) { indexOfRolesInGuidance[role] = []}); +ariaRoles.forEach(function (role) { + indexOfRolesInGuidance[role] = []; +}); let indexOfPropertiesAndStatesInExamples = {}; -ariaPropertiesAndStates.forEach(function(prop) { indexOfPropertiesAndStatesInExamples[prop] = []}); +ariaPropertiesAndStates.forEach(function (prop) { + indexOfPropertiesAndStatesInExamples[prop] = []; +}); let indexOfPropertiesAndStatesInGuidance = {}; -ariaPropertiesAndStates.forEach(function(prop) { indexOfPropertiesAndStatesInGuidance[prop] = []}); +ariaPropertiesAndStates.forEach(function (prop) { + indexOfPropertiesAndStatesInGuidance[prop] = []; +}); console.log('Generating index...'); @@ -194,9 +210,11 @@ function getRolesFromExample(data) { let code = data.substring(indexStart + 6, indexEnd).trim(); for (let i = 0; i < ariaRoles.length; i++) { - if ((getColumn(data, indexStart) === 1) && - (code == ariaRoles[i]) && - (roles.indexOf(ariaRoles[i]) < 0)) { + if ( + getColumn(data, indexStart) === 1 && + code == ariaRoles[i] && + roles.indexOf(ariaRoles[i]) < 0 + ) { roles.push(ariaRoles[i]); } } @@ -221,9 +239,11 @@ function getPropertiesAndStatesFromExample(data) { let code = data.substring(indexStart + 6, indexEnd); for (let i = 0; i < ariaPropertiesAndStates.length; i++) { - if ((getColumn(data, indexStart) === 2) && - (code.indexOf(ariaPropertiesAndStates[i]) >= 0) && - (propertiesAndStates.indexOf(ariaPropertiesAndStates[i]) < 0)) { + if ( + getColumn(data, indexStart) === 2 && + code.indexOf(ariaPropertiesAndStates[i]) >= 0 && + propertiesAndStates.indexOf(ariaPropertiesAndStates[i]) < 0 + ) { propertiesAndStates.push(ariaPropertiesAndStates[i]); } } @@ -271,7 +291,7 @@ function addExampleToPropertiesAndStates(props, example) { function addLandmarkRole(landmark, hasLabel, title, ref) { let example = { title: title, - ref: ref + ref: ref, }; addExampleToRoles(landmark, example); @@ -281,24 +301,33 @@ function addLandmarkRole(landmark, hasLabel, title, ref) { } // Index roles, properties and states used in examples -glob.sync('examples/!(landmarks)/**/!(index).html', {cwd: path.join(__dirname, '..'), nodir: true}).forEach(function (file) { - let data = fs.readFileSync(file, 'utf8'); - let ref = file.replace('examples/', '../examples/'); - let title = data.substring(data.indexOf('') + 7, data.indexOf('')) - .split('|')[0] - .replace('Examples', '') - .replace('Example of', '') - .replace('Example', '') - .trim(); - - let example = { - title: title, - ref: ref - }; - - addExampleToRoles(getRolesFromExample(data), example); - addExampleToPropertiesAndStates(getPropertiesAndStatesFromExample(data), example); -}); +glob + .sync('examples/!(landmarks)/**/!(index).html', { + cwd: path.join(__dirname, '..'), + nodir: true, + }) + .forEach(function (file) { + let data = fs.readFileSync(file, 'utf8'); + let ref = file.replace('examples/', '../examples/'); + let title = data + .substring(data.indexOf('') + 7, data.indexOf('')) + .split('|')[0] + .replace('Examples', '') + .replace('Example of', '') + .replace('Example', '') + .trim(); + + let example = { + title: title, + ref: ref, + }; + + addExampleToRoles(getRolesFromExample(data), example); + addExampleToPropertiesAndStates( + getPropertiesAndStatesFromExample(data), + example + ); + }); // Index roles, properties and states used in guidance @@ -315,7 +344,6 @@ function getClosestId(data, index) { } function addGuidanceToRole(role, url, label, id) { - let r = {}; r.title = label; r.ref = url + '#' + id; @@ -338,11 +366,10 @@ function addGuidanceToPropertyOrState(prop, url, label, id) { } function getHeaderContent(data, index) { - let content = ''; let indexStart = data.indexOf('>', index); - let indexEnd = data.indexOf(' 1 && indexEnd > 1) { content = data.substring(indexStart + 1, indexEnd).trim(); @@ -354,40 +381,41 @@ function getHeaderContent(data, index) { return content; } - function getRolesPropertiesAndStatesFromHeaders(data, url) { - function getRolesPropertiesAndStatesFromHeader(level) { - let indexStart = data.indexOf('', 0); - let indexEnd = data.indexOf('', indexStart); + let indexEnd = data.indexOf('', indexStart); while (indexStart > 1 && indexEnd > 1) { let content = getHeaderContent(data, indexStart); let contentItems = content.toLowerCase().split(' '); - ariaRoles.forEach(function(role) { + ariaRoles.forEach(function (role) { if (contentItems.indexOf(role) >= 0) { console.log('h' + level + ': ' + role + ', ' + content); addGuidanceToRole(role, url, content, getClosestId(data, indexStart)); } }); - ariaPropertiesAndStates.forEach(function(prop) { + ariaPropertiesAndStates.forEach(function (prop) { if (contentItems.indexOf(prop) >= 0) { console.log('h' + level + ': ' + prop + ', ' + content); - addGuidanceToPropertyOrState(prop, url, content , getClosestId(data, indexStart)); + addGuidanceToPropertyOrState( + prop, + url, + content, + getClosestId(data, indexStart) + ); } }); indexStart = data.indexOf('', indexEnd); if (indexStart > 0) { - indexEnd = data.indexOf('', indexStart); + indexEnd = data.indexOf('', indexStart); } } - } getRolesPropertiesAndStatesFromHeader(2); @@ -395,27 +423,31 @@ function getRolesPropertiesAndStatesFromHeaders(data, url) { getRolesPropertiesAndStatesFromHeader(4); getRolesPropertiesAndStatesFromHeader(5); getRolesPropertiesAndStatesFromHeader(6); - } - function getRolesFromDataAttributesOnHeaders(data, url) { - let indexStart = data.indexOf('data-aria-roles="', 0); let indexEnd = data.indexOf('"', indexStart + 17); while (indexStart > 1 && indexEnd > 1) { - let content = getHeaderContent(data, indexStart); - let roles = data.substring(indexStart + 17, indexEnd).trim().toLowerCase(); + let roles = data + .substring(indexStart + 17, indexEnd) + .trim() + .toLowerCase(); roles = roles.split(' '); - ariaRoles.forEach(function(role) { + ariaRoles.forEach(function (role) { if (roles.indexOf(role) >= 0) { console.log('data: ' + role + ', ' + content); - addGuidanceToRole(role, url, content+ ' (D)', getClosestId(data, indexStart)); + addGuidanceToRole( + role, + url, + content + ' (D)', + getClosestId(data, indexStart) + ); } }); @@ -425,26 +457,31 @@ function getRolesFromDataAttributesOnHeaders(data, url) { indexEnd = data.indexOf('"', indexStart + 17); } } - } function getPropertiesAndStatesFromDataAttributesOnHeaders(data, url) { - let indexStart = data.indexOf('data-aria-props="', 0); let indexEnd = data.indexOf('"', indexStart + 17); while (indexStart > 1 && indexEnd > 1) { - let content = getHeaderContent(data, indexStart); - let props = data.substring(indexStart + 17, indexEnd).trim().toLowerCase(); + let props = data + .substring(indexStart + 17, indexEnd) + .trim() + .toLowerCase(); props = props.split(' '); - ariaPropertiesAndStates.forEach(function(prop) { + ariaPropertiesAndStates.forEach(function (prop) { if (props.indexOf(prop) >= 0) { console.log('data: ' + prop + ', ' + content); - addGuidanceToPropertyOrState(prop, url, content+ ' (D)', getClosestId(data, indexStart)); + addGuidanceToPropertyOrState( + prop, + url, + content + ' (D)', + getClosestId(data, indexStart) + ); } }); @@ -454,7 +491,6 @@ function getPropertiesAndStatesFromDataAttributesOnHeaders(data, url) { indexEnd = data.indexOf('"', indexStart + 17); } } - } function getRolesPropertiesAndStatesFromGuidance(data, url) { getRolesPropertiesAndStatesFromHeaders(data, url); @@ -462,35 +498,107 @@ function getRolesPropertiesAndStatesFromGuidance(data, url) { getPropertiesAndStatesFromDataAttributesOnHeaders(data, url); } -let data = fs.readFileSync(path.join(__dirname, '../aria-practices.html'), 'utf8'); +let data = fs.readFileSync( + path.join(__dirname, '../aria-practices.html'), + 'utf8' +); getRolesPropertiesAndStatesFromGuidance(data, '../aria-practices.html'); - // Add landmark examples, since they are a different format -addLandmarkRole(['banner'], false, 'Banner Landmark', '../examples/landmarks/banner.html'); -addGuidanceToRole('banner', '../aria-practices.html', 'Banner', 'aria_lh_banner'); - -addLandmarkRole(['complementary'], true, 'Complementary Landmark', '../examples/landmarks/complementary.html'); -addGuidanceToRole('complementary', '../aria-practices.html', 'Complementary', 'aria_lh_complemtary'); - -addLandmarkRole(['contentinfo'], false, 'Contentinfo Landmark', '../examples/landmarks/contentinfo.html'); -addGuidanceToRole('contentinfo', '../aria-practices.html', 'Contentinfo', 'aria_lh_contentinfo'); - -addLandmarkRole(['form'], true, 'Form Landmark', '../examples/landmarks/form.html'); +addLandmarkRole( + ['banner'], + false, + 'Banner Landmark', + '../examples/landmarks/banner.html' +); +addGuidanceToRole( + 'banner', + '../aria-practices.html', + 'Banner', + 'aria_lh_banner' +); + +addLandmarkRole( + ['complementary'], + true, + 'Complementary Landmark', + '../examples/landmarks/complementary.html' +); +addGuidanceToRole( + 'complementary', + '../aria-practices.html', + 'Complementary', + 'aria_lh_complemtary' +); + +addLandmarkRole( + ['contentinfo'], + false, + 'Contentinfo Landmark', + '../examples/landmarks/contentinfo.html' +); +addGuidanceToRole( + 'contentinfo', + '../aria-practices.html', + 'Contentinfo', + 'aria_lh_contentinfo' +); + +addLandmarkRole( + ['form'], + true, + 'Form Landmark', + '../examples/landmarks/form.html' +); addGuidanceToRole('form', '../aria-practices.html', 'Form', 'aria_lh_form'); -addLandmarkRole(['main'], true, 'Main Landmark', '../examples/landmarks/main.html'); +addLandmarkRole( + ['main'], + true, + 'Main Landmark', + '../examples/landmarks/main.html' +); addGuidanceToRole('main', '../aria-practices.html', 'Main', 'aria_lh_main'); -addLandmarkRole(['navigation'], true, 'Navigation Landmark', '../examples/landmarks/navigation.html'); -addGuidanceToRole('navigation', '../aria-practices.html', 'Navigation', 'aria_lh_navigation'); - -addLandmarkRole(['region'], true, 'Region Landmark', '../examples/landmarks/region.html'); -addGuidanceToRole('region', '../aria-practices.html', 'Region', 'aria_lh_region'); - -addLandmarkRole(['search'], true, 'Search Landmark', '../examples/landmarks/search.html'); -addGuidanceToRole('search', '../aria-practices.html', 'Search', 'aria_lh_search'); +addLandmarkRole( + ['navigation'], + true, + 'Navigation Landmark', + '../examples/landmarks/navigation.html' +); +addGuidanceToRole( + 'navigation', + '../aria-practices.html', + 'Navigation', + 'aria_lh_navigation' +); + +addLandmarkRole( + ['region'], + true, + 'Region Landmark', + '../examples/landmarks/region.html' +); +addGuidanceToRole( + 'region', + '../aria-practices.html', + 'Region', + 'aria_lh_region' +); + +addLandmarkRole( + ['search'], + true, + 'Search Landmark', + '../examples/landmarks/search.html' +); +addGuidanceToRole( + 'search', + '../aria-practices.html', + 'Search', + 'aria_lh_search' +); function getListItem(item) { return ` @@ -502,10 +610,9 @@ function getListHTML(list) { if (list.length === 1) { html = `${list[0].title}\n`; - } - else { + } else { if (list.length > 1) { - html = `
        ${list.map(getListItem).join('')} + html = `
          ${list.map(getListItem).join('')}
        \n`; } } @@ -530,7 +637,6 @@ let RoleWithNoExamples = sortedRoles.reduce(function (set, role) { } return `${set}`; - }, ''); $('#roles_with_no_examples_ul').html(RoleWithNoExamples); @@ -539,9 +645,11 @@ let RoleWithOneExample = sortedRoles.reduce(function (set, role) { let examples = indexOfRolesInExamples[role]; let guidance = indexOfRolesInGuidance[role]; - if ((examples.length === 1 && guidance.length === 0) || - (examples.length === 0 && guidance.length === 1) || - (examples.length === 1 && guidance.length === 1)) { + if ( + (examples.length === 1 && guidance.length === 0) || + (examples.length === 0 && guidance.length === 1) || + (examples.length === 1 && guidance.length === 1) + ) { countOneExample += 1; return `${set} @@ -552,7 +660,6 @@ let RoleWithOneExample = sortedRoles.reduce(function (set, role) { } return `${set}`; - }, ''); $('#roles_with_one_example_tbody').html(RoleWithOneExample); @@ -578,18 +685,24 @@ $('#roles_with_more_than_one_tbody').html(RoleWithMoreThanOneExample); $('.roles_with_no_examples_count').html(countNoExamples.toString()); $('.roles_with_one_example_count').html(countOneExample.toString()); -$('.roles_with_more_than_one_examples_count').html(countMoreThanOneExample.toString()); +$('.roles_with_more_than_one_examples_count').html( + countMoreThanOneExample.toString() +); // Properties and States -let sortedPropertiesAndStates = Object.getOwnPropertyNames(indexOfPropertiesAndStatesInExamples) - .sort(); +let sortedPropertiesAndStates = Object.getOwnPropertyNames( + indexOfPropertiesAndStatesInExamples +).sort(); countNoExamples = 0; countOneExample = 0; countMoreThanOneExample = 0; -let PropsWithNoExamples = sortedPropertiesAndStates.reduce(function (set, prop) { +let PropsWithNoExamples = sortedPropertiesAndStates.reduce(function ( + set, + prop +) { let examples = indexOfPropertiesAndStatesInExamples[prop]; let guidance = indexOfPropertiesAndStatesInGuidance[prop]; @@ -600,19 +713,24 @@ let PropsWithNoExamples = sortedPropertiesAndStates.reduce(function (set, prop) } return `${set}`; - -}, ''); +}, +''); $('#props_with_no_examples_ul').html(PropsWithNoExamples); $('.props_with_no_examples_count').html(countNoExamples.toString()); -let PropsWithOneExample = sortedPropertiesAndStates.reduce(function (set, prop) { +let PropsWithOneExample = sortedPropertiesAndStates.reduce(function ( + set, + prop +) { let examples = indexOfPropertiesAndStatesInExamples[prop]; let guidance = indexOfPropertiesAndStatesInGuidance[prop]; - if ((examples.length === 1 && guidance.length === 0) || - (examples.length === 0 && guidance.length === 1) || - (examples.length === 1 && guidance.length === 1)) { + if ( + (examples.length === 1 && guidance.length === 0) || + (examples.length === 0 && guidance.length === 1) || + (examples.length === 1 && guidance.length === 1) + ) { countOneExample += 1; return `${set} @@ -623,17 +741,20 @@ let PropsWithOneExample = sortedPropertiesAndStates.reduce(function (set, prop) } return `${set}`; - -}, ''); +}, +''); $('#props_with_one_example_tbody').html(PropsWithOneExample); $('.props_with_one_example_count').html(countOneExample.toString()); -let PropsWithMoreThanOneExample = sortedPropertiesAndStates.reduce(function (set, prop) { +let PropsWithMoreThanOneExample = sortedPropertiesAndStates.reduce(function ( + set, + prop +) { let examples = indexOfPropertiesAndStatesInExamples[prop]; let guidance = indexOfPropertiesAndStatesInGuidance[prop]; - if ((examples.length > 1 || guidance.length > 1)) { + if (examples.length > 1 || guidance.length > 1) { countMoreThanOneExample += 1; return `${set} @@ -645,18 +766,21 @@ let PropsWithMoreThanOneExample = sortedPropertiesAndStates.reduce(function (set } return `${set}`; - -}, ''); +}, +''); $('#props_with_more_than_one_tbody').html(PropsWithMoreThanOneExample); -$('.props_with_more_than_one_examples_count').html(countMoreThanOneExample.toString()); - - +$('.props_with_more_than_one_examples_count').html( + countMoreThanOneExample.toString() +); // cheeio seems to fold the doctype lines despite the template const result = $.html() - .replace('', '\n') - .replace('', '\n') + .replace('', '\n') + .replace( + '', + '\n' + ); fs.writeFile(exampleFilePath, result, function (err) { if (err) { @@ -664,44 +788,50 @@ fs.writeFile(exampleFilePath, result, function (err) { } }); - // Output CSV files -let roles = '"Role","Guidance","Examples","References"\n' +let roles = '"Role","Guidance","Examples","References"\n'; roles += sortedRoles.reduce(function (line, role) { let examples = indexOfRolesInExamples[role]; let guidance = indexOfRolesInGuidance[role]; - let csvExampleTitles = examples.reduce(function (set, e) { return `${set},"Example: ${e.title}"`}, ''); - let csvGuidanceTitles = guidance.reduce(function (set, g) { return `${set},"Guidance: ${g.title}"`}, ''); + let csvExampleTitles = examples.reduce(function (set, e) { + return `${set},"Example: ${e.title}"`; + }, ''); + let csvGuidanceTitles = guidance.reduce(function (set, g) { + return `${set},"Guidance: ${g.title}"`; + }, ''); return `${line}"${role}","${guidance.length}","${examples.length}"${csvGuidanceTitles}${csvExampleTitles}\n`; }, ''); fs.writeFile(csvRoleFilePath, roles, (err) => { if (err) { - console.error(err) - return + console.error(err); + return; } //file written successfully -}) +}); -let props = '"Property or State","Guidance","Examples","References"\n' +let props = '"Property or State","Guidance","Examples","References"\n'; props += sortedPropertiesAndStates.reduce(function (line, prop) { let examples = indexOfPropertiesAndStatesInExamples[prop]; let guidance = indexOfPropertiesAndStatesInGuidance[prop]; - let csvExampleTitles = examples.reduce(function (set, e) { return `${set},"Example: ${e.title}"`}, ''); - let csvGuidanceTitles = guidance.reduce(function (set, g) { return `${set},"Guidance: ${g.title}"`}, ''); + let csvExampleTitles = examples.reduce(function (set, e) { + return `${set},"Example: ${e.title}"`; + }, ''); + let csvGuidanceTitles = guidance.reduce(function (set, g) { + return `${set},"Guidance: ${g.title}"`; + }, ''); return `${line}"${prop}","${guidance.length}","${examples.length}"${csvGuidanceTitles}${csvExampleTitles}\n`; - }, ''); fs.writeFile(csvPropFilePath, props, (err) => { if (err) { - console.error(err) - return + console.error(err); + return; } //file written successfully -}) +}); diff --git a/scripts/reference-tables.js b/scripts/reference-tables.js index 4f1418f53f..80c4e03b69 100644 --- a/scripts/reference-tables.js +++ b/scripts/reference-tables.js @@ -91,7 +91,7 @@ const ariaRoles = [ 'tooltip', 'tree', 'treegrid', - 'treeitem' + 'treeitem', ]; const ariaPropertiesAndStates = [ @@ -142,7 +142,7 @@ const ariaPropertiesAndStates = [ 'aria-valuemax', 'aria-valuemin', 'aria-valuenow', - 'aria-valuetext' + 'aria-valuetext', ]; let indexOfRoles = {}; @@ -150,7 +150,7 @@ let indexOfPropertiesAndStates = {}; console.log('Generating index...'); -function getColumn (data, indexStart) { +function getColumn(data, indexStart) { let count = 0; let index = data.lastIndexOf('', 0); @@ -180,9 +180,11 @@ function getRoles (data) { let code = data.substring(indexStart + 6, indexEnd).trim(); for (let i = 0; i < ariaRoles.length; i++) { - if ((getColumn(data, indexStart) === 1) && - (code == ariaRoles[i]) && - (roles.indexOf(ariaRoles[i]) < 0)) { + if ( + getColumn(data, indexStart) === 1 && + code == ariaRoles[i] && + roles.indexOf(ariaRoles[i]) < 0 + ) { roles.push(ariaRoles[i]); } } @@ -197,7 +199,7 @@ function getRoles (data) { return roles; } -function getPropertiesAndStates (data) { +function getPropertiesAndStates(data) { let propertiesAndStates = []; let indexStart = data.indexOf('', 0); @@ -207,9 +209,11 @@ function getPropertiesAndStates (data) { let code = data.substring(indexStart + 6, indexEnd); for (let i = 0; i < ariaPropertiesAndStates.length; i++) { - if ((getColumn(data, indexStart) === 2) && - (code.indexOf(ariaPropertiesAndStates[i]) >= 0) && - (propertiesAndStates.indexOf(ariaPropertiesAndStates[i]) < 0)) { + if ( + getColumn(data, indexStart) === 2 && + code.indexOf(ariaPropertiesAndStates[i]) >= 0 && + propertiesAndStates.indexOf(ariaPropertiesAndStates[i]) < 0 + ) { propertiesAndStates.push(ariaPropertiesAndStates[i]); } } @@ -224,7 +228,7 @@ function getPropertiesAndStates (data) { return propertiesAndStates; } -function addExampleToRoles (roles, example) { +function addExampleToRoles(roles, example) { for (let i = 0; i < roles.length; i++) { let role = roles[i]; @@ -239,7 +243,7 @@ function addExampleToRoles (roles, example) { } } -function addExampleToPropertiesAndStates (props, example) { +function addExampleToPropertiesAndStates(props, example) { for (let i = 0; i < props.length; i++) { let prop = props[i]; @@ -254,48 +258,69 @@ function addExampleToPropertiesAndStates (props, example) { } } -function addLandmarkRole (landmark, hasLabel, title, ref) { +function addLandmarkRole(landmark, hasLabel, title, ref) { let example = { title: title, - ref: ref + ref: ref, }; addExampleToRoles(landmark, example); if (hasLabel) { - addExampleToPropertiesAndStates([ 'aria-labelledby' ], example); + addExampleToPropertiesAndStates(['aria-labelledby'], example); } } -glob.sync('examples/!(landmarks)/**/!(index).html', {cwd: path.join(__dirname, '..'), nodir: true}).forEach(function (file) { - let data = fs.readFileSync(file, 'utf8'); - let ref = file.replace('examples/', ''); - let title = data.substring(data.indexOf('') + 7, data.indexOf('')) - .split('|')[0] - .replace('Examples', '') - .replace('Example of', '') - .replace('Example', '') - .trim(); - - let example = { - title: title, - ref: ref - }; - - addExampleToRoles(getRoles(data), example); - addExampleToPropertiesAndStates(getPropertiesAndStates(data), example); -}); +glob + .sync('examples/!(landmarks)/**/!(index).html', { + cwd: path.join(__dirname, '..'), + nodir: true, + }) + .forEach(function (file) { + let data = fs.readFileSync(file, 'utf8'); + let ref = file.replace('examples/', ''); + let title = data + .substring(data.indexOf('') + 7, data.indexOf('')) + .split('|')[0] + .replace('Examples', '') + .replace('Example of', '') + .replace('Example', '') + .trim(); + + let example = { + title: title, + ref: ref, + }; + + addExampleToRoles(getRoles(data), example); + addExampleToPropertiesAndStates(getPropertiesAndStates(data), example); + }); // Add landmark examples, since they are a different format -addLandmarkRole([ 'banner' ], false, 'Banner Landmark', 'landmarks/banner.html'); -addLandmarkRole([ 'complementary' ], true, 'Complementary Landmark', 'landmarks/complementary.html'); -addLandmarkRole([ 'contentinfo' ], false, 'Contentinfo Landmark', 'landmarks/contentinfo.html'); -addLandmarkRole([ 'form' ], true, 'Form Landmark', 'landmarks/form.html'); -addLandmarkRole([ 'main' ], true, 'Main Landmark', 'landmarks/main.html'); -addLandmarkRole([ 'navigation' ], true, 'Navigation Landmark', 'landmarks/navigation.html'); -addLandmarkRole([ 'region' ], true, 'Region Landmark', 'landmarks/region.html'); -addLandmarkRole([ 'search' ], true, 'Search Landmark', 'landmarks/search.html'); - -function exampleListItem (item) { +addLandmarkRole(['banner'], false, 'Banner Landmark', 'landmarks/banner.html'); +addLandmarkRole( + ['complementary'], + true, + 'Complementary Landmark', + 'landmarks/complementary.html' +); +addLandmarkRole( + ['contentinfo'], + false, + 'Contentinfo Landmark', + 'landmarks/contentinfo.html' +); +addLandmarkRole(['form'], true, 'Form Landmark', 'landmarks/form.html'); +addLandmarkRole(['main'], true, 'Main Landmark', 'landmarks/main.html'); +addLandmarkRole( + ['navigation'], + true, + 'Navigation Landmark', + 'landmarks/navigation.html' +); +addLandmarkRole(['region'], true, 'Region Landmark', 'landmarks/region.html'); +addLandmarkRole(['search'], true, 'Search Landmark', 'landmarks/search.html'); + +function exampleListItem(item) { return `
      • ${item.title}
      • `; } @@ -308,8 +333,7 @@ let examplesByRole = sortedRoles.reduce(function (set, role) { let examplesHTML = ''; if (examples.length === 1) { examplesHTML = `${examples[0].title}`; - } - else { + } else { examplesHTML = `
          ${examples.map(exampleListItem).join('')}
        \n `; @@ -323,8 +347,9 @@ let examplesByRole = sortedRoles.reduce(function (set, role) { $('#examples_by_role_tbody').html(examplesByRole); -let sortedPropertiesAndStates = Object.getOwnPropertyNames(indexOfPropertiesAndStates) - .sort(); +let sortedPropertiesAndStates = Object.getOwnPropertyNames( + indexOfPropertiesAndStates +).sort(); let examplesByProps = sortedPropertiesAndStates.reduce(function (set, prop) { let examples = indexOfPropertiesAndStates[prop]; @@ -332,8 +357,7 @@ let examplesByProps = sortedPropertiesAndStates.reduce(function (set, prop) { let examplesHTML = ''; if (examples.length === 1) { examplesHTML = `${examples[0].title}`; - } - else { + } else { examplesHTML = `
          ${examples.map(exampleListItem).join('')}
        \n `; @@ -350,7 +374,10 @@ $('#examples_by_props_tbody').html(examplesByProps); // cheeio seems to fold the doctype lines despite the template const result = $.html() .replace('', '\n') - .replace('', '\n'); + .replace( + '', + '\n' + ); fs.writeFile(exampleFilePath, result, function (err) { if (err) { diff --git a/test/index.js b/test/index.js index 04ff221182..921650e523 100644 --- a/test/index.js +++ b/test/index.js @@ -10,7 +10,7 @@ const queryElement = require('./util/queryElement'); const queryElements = require('./util/queryElements'); let session, geckodriver; -const firefoxArgs = process.env.CI ? [ '-headless' ] : []; +const firefoxArgs = process.env.CI ? ['-headless'] : []; const testWaitTime = parseInt(process.env.TEST_WAIT_TIME) || 500; const coverageReportRun = process.env.REGRESSION_COVERAGE_REPORT; @@ -21,8 +21,8 @@ if (!coverageReportRun) { .usingServer('http://localhost:' + geckodriver.port) .withCapabilities({ 'moz:firefoxOptions': { - args: firefoxArgs - } + args: firefoxArgs, + }, }) .forBrowser('firefox') .build(); @@ -37,8 +37,9 @@ if (!coverageReportRun) { }); test.after.always(() => { - return Promise.resolve(session && session.close()) - .then(() => geckodriver && geckodriver.stop()); + return Promise.resolve(session && session.close()).then( + () => geckodriver && geckodriver.stop() + ); }); } @@ -89,7 +90,8 @@ const _ariaTest = (desc, page, testId, body, failing) => { const assert = require('assert'); assert( (await t.context.queryElements(t, selector)).length, - 'Cannot find behavior description for this test in example page:' + testId + 'Cannot find behavior description for this test in example page:' + + testId ); } diff --git a/test/tests/accordion_accordion.js b/test/tests/accordion_accordion.js index eeb8f278f5..7de79f683e 100644 --- a/test/tests/accordion_accordion.js +++ b/test/tests/accordion_accordion.js @@ -10,22 +10,36 @@ const exampleFile = 'accordion/accordion.html'; const ex = { buttonSelector: '#coding-arena button', panelSelector: '#coding-arena [role="region"]', - buttonsInOrder: [ - '#accordion1id', - '#accordion2id', - '#accordion3id' + buttonsInOrder: ['#accordion1id', '#accordion2id', '#accordion3id'], + firstPanelInputSelectors: [ + '#cufc1', + '#cufc2', + '#cufc3', + '#cufc4', + '#cufc5', + '#cufc6', + ], + secondPanelInputSelectors: [ + '#b-add1', + '#b-add2', + '#b-city', + '#b-state', + '#b-zip', + ], + thirdPanelInputSelectors: [ + '#m-add1', + '#m-add2', + '#m-city', + '#m-state', + '#m-zip', ], - firstPanelInputSelectors: ['#cufc1', '#cufc2', '#cufc3', '#cufc4', '#cufc5', '#cufc6'], - secondPanelInputSelectors: ['#b-add1', '#b-add2', '#b-city', '#b-state', '#b-zip'], - thirdPanelInputSelectors: ['#m-add1', '#m-add2', '#m-city', '#m-state', '#m-zip'] }; const parentTagName = function (t, element) { - return t.context.session.executeScript( - async function () { - const buttonEl = arguments[0]; - return buttonEl.parentElement.tagName; - }, element); + return t.context.session.executeScript(async function () { + const buttonEl = arguments[0]; + return buttonEl.parentElement.tagName; + }, element); }; const focusMatchesElement = async function (t, selector) { @@ -43,396 +57,507 @@ const focusMatchesElement = async function (t, selector) { // Attributes -ariaTest('h3 element should wrap accordion button', exampleFile, 'h3-element', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - for (let button of buttons) { - t.is( - await parentTagName(t, button), - 'H3', - 'Parent of button ' + (await button.getText()) + 'should be an H3 element' - ); - } -}); - -ariaTest('aria-expanded on button element', exampleFile, 'button-aria-expanded', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - for (let expandIndex = 0; expandIndex < buttons.length; expandIndex++) { - - // Click a heading to expand the section - await buttons[expandIndex].click(); +ariaTest( + 'h3 element should wrap accordion button', + exampleFile, + 'h3-element', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); - for (let index = 0; index < buttons.length; index++) { - const expandedValue = index === expandIndex ? 'true' : 'false'; + for (let button of buttons) { t.is( - await buttons[index].getAttribute('aria-expanded'), - expandedValue, - 'Accordion button at index ' + expandIndex + ' has been clicked, therefore ' + - '"aria-expanded" on button ' + index + ' should be "' + expandedValue + await parentTagName(t, button), + 'H3', + 'Parent of button ' + + (await button.getText()) + + 'should be an H3 element' ); } } -}); - -ariaTest('aria-controls on button element', exampleFile, 'button-aria-controls', async (t) => { - - await assertAriaControls(t, ex.buttonSelector); -}); - -ariaTest('"aria-disabled" set on expanded sections', exampleFile, 'button-aria-disabled', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - for (let expandIndex = 0; expandIndex < buttons.length; expandIndex++) { - - // Click a heading to expand the section - await buttons[expandIndex].click(); - - for (let index = 0; index < buttons.length; index++) { - const disabledValue = index === expandIndex ? 'true' : null; - t.is( - await buttons[index].getAttribute('aria-disabled'), - disabledValue, - 'Accordion button at index ' + expandIndex + ' has been clicked, therefore ' + - '"aria-disabled" on button ' + index + ' should be "' + disabledValue - ); +); + +ariaTest( + 'aria-expanded on button element', + exampleFile, + 'button-aria-expanded', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + for (let expandIndex = 0; expandIndex < buttons.length; expandIndex++) { + // Click a heading to expand the section + await buttons[expandIndex].click(); + + for (let index = 0; index < buttons.length; index++) { + const expandedValue = index === expandIndex ? 'true' : 'false'; + t.is( + await buttons[index].getAttribute('aria-expanded'), + expandedValue, + 'Accordion button at index ' + + expandIndex + + ' has been clicked, therefore ' + + '"aria-expanded" on button ' + + index + + ' should be "' + + expandedValue + ); + } } } -}); - -ariaTest('role "region" exists on accordion panels', exampleFile, 'region-role', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const panelIds = []; - for (let button of buttons) { - panelIds.push(await button.getAttribute('aria-controls')); - } - - for (let panelId of panelIds) { - t.is( - await t.context.session.findElement(By.id(panelId)).getAttribute('role'), - 'region', - 'Panel with id "' + panelId + '" should have role="region"' - ); - } -}); - -ariaTest('"aria-labelledby" on region', exampleFile, 'region-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.panelSelector); -}); - - -// Keys - -ariaTest('ENTER key expands section', exampleFile, 'key-enter-or-space', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const panels = await t.context.queryElements(t, ex.panelSelector); - - for (let expandIndex of [1, 2, 0]) { - await buttons[expandIndex].sendKeys(Key.ENTER); - - t.true( - await panels[expandIndex].isDisplayed(), - 'Sending key ENTER to button at index ' + expandIndex + ' should expand the region.' - ); - - t.is( - await buttons[expandIndex].getAttribute('aria-expanded'), - 'true', - 'Sending key ENTER to button at index ' + expandIndex + ' set aria-expanded to "true".' - ); - - t.is( - await buttons[expandIndex].getAttribute('aria-disabled'), - 'true', - 'Sending key ENTER to button at index ' + expandIndex + ' set aria-disable to "true".' - ); - - await buttons[expandIndex].sendKeys(Key.ENTER); - - t.true( - await panels[expandIndex].isDisplayed(), - 'Sending key ENTER twice to button at index ' + expandIndex + ' do nothing.' - ); - } -}); - -ariaTest('SPACE key expands section', exampleFile, 'key-enter-or-space', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const panels = await t.context.queryElements(t, ex.panelSelector); - - for (let expandIndex of [1, 2, 0]) { - await buttons[expandIndex].sendKeys(Key.SPACE); - - t.true( - await panels[expandIndex].isDisplayed(), - 'Sending key SPACE to button at index ' + expandIndex + ' should expand the region.' - ); - - t.is( - await buttons[expandIndex].getAttribute('aria-expanded'), - 'true', - 'Sending key SPACE to button at index ' + expandIndex + ' set aria-expanded to "true".' - ); - - t.is( - await buttons[expandIndex].getAttribute('aria-disabled'), - 'true', - 'Sending key SPACE to button at index ' + expandIndex + ' set aria-disable to "true".' - ); - - await buttons[expandIndex].sendKeys(Key.SPACE); - - t.true( - await panels[expandIndex].isDisplayed(), - 'Sending key SPACE twice to button at index ' + expandIndex + ' do nothing.' - ); +); + +ariaTest( + 'aria-controls on button element', + exampleFile, + 'button-aria-controls', + async (t) => { + await assertAriaControls(t, ex.buttonSelector); } -}); - -ariaTest('TAB moves focus between headers and displayed inputs', exampleFile, 'key-tab', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - // Open a panel - await buttons[0].click(); - - let elementsInOrder = [ - ex.buttonsInOrder[0], - ...ex.firstPanelInputSelectors, - ex.buttonsInOrder[1], - ex.buttonsInOrder[2] - ]; - - // Send TAB to the first panel button - let firstElement = elementsInOrder.shift(); - await t.context.session.findElement(By.css(firstElement)).sendKeys(Key.TAB); - - // Confirm focus moves through remaining items - for (let itemSelector of elementsInOrder) { - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should reach element: ' + itemSelector - ); - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.TAB); +); + +ariaTest( + '"aria-disabled" set on expanded sections', + exampleFile, + 'button-aria-disabled', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + for (let expandIndex = 0; expandIndex < buttons.length; expandIndex++) { + // Click a heading to expand the section + await buttons[expandIndex].click(); + + for (let index = 0; index < buttons.length; index++) { + const disabledValue = index === expandIndex ? 'true' : null; + t.is( + await buttons[index].getAttribute('aria-disabled'), + disabledValue, + 'Accordion button at index ' + + expandIndex + + ' has been clicked, therefore ' + + '"aria-disabled" on button ' + + index + + ' should be "' + + disabledValue + ); + } + } } +); + +ariaTest( + 'role "region" exists on accordion panels', + exampleFile, + 'region-role', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const panelIds = []; + for (let button of buttons) { + panelIds.push(await button.getAttribute('aria-controls')); + } - // Open the next panel - await buttons[1].click(); - - elementsInOrder = [ - ex.buttonsInOrder[0], - ex.buttonsInOrder[1], - ...ex.secondPanelInputSelectors, - ex.buttonsInOrder[2] - ]; - - // Send TAB to the first panel button - firstElement = elementsInOrder.shift(); - await t.context.session.findElement(By.css(firstElement)).sendKeys(Key.TAB); - - // Confirm focus moves through remaining items - for (let itemSelector of elementsInOrder) { - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should reach element: ' + itemSelector - ); - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.TAB); + for (let panelId of panelIds) { + t.is( + await t.context.session + .findElement(By.id(panelId)) + .getAttribute('role'), + 'region', + 'Panel with id "' + panelId + '" should have role="region"' + ); + } } +); - // Open the next panel - await buttons[2].click(); - - elementsInOrder = [ - ex.buttonsInOrder[0], - ex.buttonsInOrder[1], - ex.buttonsInOrder[2], - ...ex.thirdPanelInputSelectors - ]; - - // Send TAB to the first panel button - firstElement = elementsInOrder.shift(); - await t.context.session.findElement(By.css(firstElement)).sendKeys(Key.TAB); - - // Confirm focus moves through remaining items - for (let itemSelector of elementsInOrder) { - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should reach element: ' + itemSelector - ); - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.TAB); +ariaTest( + '"aria-labelledby" on region', + exampleFile, + 'region-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.panelSelector); } -}); - -ariaTest('SHIFT+TAB moves focus between headers and displayed inputs', exampleFile, 'key-shift-tab', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - // Open a panel - await buttons[0].click(); +); - let elementsInOrder = [ - ex.buttonsInOrder[0], - ...ex.firstPanelInputSelectors, - ex.buttonsInOrder[1], - ex.buttonsInOrder[2] - ]; - - // Send SHIFT-TAB to the last panel button - let lastElement = elementsInOrder.pop(); - await t.context.session.findElement(By.css(lastElement)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); +// Keys - // Confirm focus moves through remaining items - for (let index = elementsInOrder.length - 1; index >= 0; index--) { - let itemSelector = elementsInOrder[index]; - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should reach element: ' + itemSelector - ); - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - } +ariaTest( + 'ENTER key expands section', + exampleFile, + 'key-enter-or-space', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const panels = await t.context.queryElements(t, ex.panelSelector); + + for (let expandIndex of [1, 2, 0]) { + await buttons[expandIndex].sendKeys(Key.ENTER); + + t.true( + await panels[expandIndex].isDisplayed(), + 'Sending key ENTER to button at index ' + + expandIndex + + ' should expand the region.' + ); - // Open the next panel - await buttons[1].click(); + t.is( + await buttons[expandIndex].getAttribute('aria-expanded'), + 'true', + 'Sending key ENTER to button at index ' + + expandIndex + + ' set aria-expanded to "true".' + ); - elementsInOrder = [ - ex.buttonsInOrder[0], - ex.buttonsInOrder[1], - ...ex.secondPanelInputSelectors, - ex.buttonsInOrder[2] - ]; + t.is( + await buttons[expandIndex].getAttribute('aria-disabled'), + 'true', + 'Sending key ENTER to button at index ' + + expandIndex + + ' set aria-disable to "true".' + ); - // Send TAB to the last panel button - lastElement = elementsInOrder.pop(); - await t.context.session.findElement(By.css(lastElement)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + await buttons[expandIndex].sendKeys(Key.ENTER); - // Confirm focus moves through remaining items - for (let index = elementsInOrder.length - 1; index >= 0; index--) { - let itemSelector = elementsInOrder[index]; - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should reach element: ' + itemSelector - ); - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + t.true( + await panels[expandIndex].isDisplayed(), + 'Sending key ENTER twice to button at index ' + + expandIndex + + ' do nothing.' + ); + } } +); + +ariaTest( + 'SPACE key expands section', + exampleFile, + 'key-enter-or-space', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const panels = await t.context.queryElements(t, ex.panelSelector); + + for (let expandIndex of [1, 2, 0]) { + await buttons[expandIndex].sendKeys(Key.SPACE); + + t.true( + await panels[expandIndex].isDisplayed(), + 'Sending key SPACE to button at index ' + + expandIndex + + ' should expand the region.' + ); - // Open the next panel - await buttons[2].click(); + t.is( + await buttons[expandIndex].getAttribute('aria-expanded'), + 'true', + 'Sending key SPACE to button at index ' + + expandIndex + + ' set aria-expanded to "true".' + ); - elementsInOrder = [ - ex.buttonsInOrder[0], - ex.buttonsInOrder[1], - ex.buttonsInOrder[2], - ...ex.thirdPanelInputSelectors - ]; + t.is( + await buttons[expandIndex].getAttribute('aria-disabled'), + 'true', + 'Sending key SPACE to button at index ' + + expandIndex + + ' set aria-disable to "true".' + ); - // Send TAB to the last focusable item - lastElement = elementsInOrder.pop(); - await t.context.session.findElement(By.css(lastElement)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + await buttons[expandIndex].sendKeys(Key.SPACE); - // Confirm focus moves through remaining items - for (let index = elementsInOrder.length - 1; index >= 0; index--) { - let itemSelector = elementsInOrder[index]; - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should reach element: ' + itemSelector - ); - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + t.true( + await panels[expandIndex].isDisplayed(), + 'Sending key SPACE twice to button at index ' + + expandIndex + + ' do nothing.' + ); + } } -}); - -ariaTest('DOWN ARROW moves focus between headers', exampleFile, 'key-down-arrow', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - // Confirm focus moves through remaining items - for (let index = 0; index < ex.buttonsInOrder.length - 1; index++) { +); + +ariaTest( + 'TAB moves focus between headers and displayed inputs', + exampleFile, + 'key-tab', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + // Open a panel + await buttons[0].click(); + + let elementsInOrder = [ + ex.buttonsInOrder[0], + ...ex.firstPanelInputSelectors, + ex.buttonsInOrder[1], + ex.buttonsInOrder[2], + ]; + + // Send TAB to the first panel button + let firstElement = elementsInOrder.shift(); + await t.context.session.findElement(By.css(firstElement)).sendKeys(Key.TAB); + + // Confirm focus moves through remaining items + for (let itemSelector of elementsInOrder) { + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should reach element: ' + itemSelector + ); + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.TAB); + } - let itemSelector = ex.buttonsInOrder[index]; - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.ARROW_DOWN); + // Open the next panel + await buttons[1].click(); + + elementsInOrder = [ + ex.buttonsInOrder[0], + ex.buttonsInOrder[1], + ...ex.secondPanelInputSelectors, + ex.buttonsInOrder[2], + ]; + + // Send TAB to the first panel button + firstElement = elementsInOrder.shift(); + await t.context.session.findElement(By.css(firstElement)).sendKeys(Key.TAB); + + // Confirm focus moves through remaining items + for (let itemSelector of elementsInOrder) { + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should reach element: ' + itemSelector + ); + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.TAB); + } - t.true( - await focusMatchesElement(t, ex.buttonsInOrder[index + 1]), - 'Focus should reach element: ' + ex.buttonsInOrder[index + 1] - ); + // Open the next panel + await buttons[2].click(); + + elementsInOrder = [ + ex.buttonsInOrder[0], + ex.buttonsInOrder[1], + ex.buttonsInOrder[2], + ...ex.thirdPanelInputSelectors, + ]; + + // Send TAB to the first panel button + firstElement = elementsInOrder.shift(); + await t.context.session.findElement(By.css(firstElement)).sendKeys(Key.TAB); + + // Confirm focus moves through remaining items + for (let itemSelector of elementsInOrder) { + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should reach element: ' + itemSelector + ); + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.TAB); + } } +); + +ariaTest( + 'SHIFT+TAB moves focus between headers and displayed inputs', + exampleFile, + 'key-shift-tab', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + // Open a panel + await buttons[0].click(); + + let elementsInOrder = [ + ex.buttonsInOrder[0], + ...ex.firstPanelInputSelectors, + ex.buttonsInOrder[1], + ex.buttonsInOrder[2], + ]; + + // Send SHIFT-TAB to the last panel button + let lastElement = elementsInOrder.pop(); + await t.context.session + .findElement(By.css(lastElement)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + // Confirm focus moves through remaining items + for (let index = elementsInOrder.length - 1; index >= 0; index--) { + let itemSelector = elementsInOrder[index]; + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should reach element: ' + itemSelector + ); + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + } - // Focus should wrap to first item - let itemSelector = ex.buttonsInOrder[ex.buttonsInOrder.length - 1]; - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.ARROW_DOWN); - - t.true( - await focusMatchesElement(t, ex.buttonsInOrder[0]), - 'Focus should reach element: ' + ex.buttonsInOrder[0] - ); - -}); - -ariaTest('UP ARROW moves focus between headers', exampleFile, 'key-up-arrow', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - // Confirm focus moves through remaining items - for (let index = ex.buttonsInOrder.length - 1; index > 0; index--) { - - let itemSelector = ex.buttonsInOrder[index]; - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.ARROW_UP); + // Open the next panel + await buttons[1].click(); + + elementsInOrder = [ + ex.buttonsInOrder[0], + ex.buttonsInOrder[1], + ...ex.secondPanelInputSelectors, + ex.buttonsInOrder[2], + ]; + + // Send TAB to the last panel button + lastElement = elementsInOrder.pop(); + await t.context.session + .findElement(By.css(lastElement)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + // Confirm focus moves through remaining items + for (let index = elementsInOrder.length - 1; index >= 0; index--) { + let itemSelector = elementsInOrder[index]; + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should reach element: ' + itemSelector + ); + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + } - t.true( - await focusMatchesElement(t, ex.buttonsInOrder[index - 1]), - 'Focus should reach element: ' + ex.buttonsInOrder[index - 1] - ); + // Open the next panel + await buttons[2].click(); + + elementsInOrder = [ + ex.buttonsInOrder[0], + ex.buttonsInOrder[1], + ex.buttonsInOrder[2], + ...ex.thirdPanelInputSelectors, + ]; + + // Send TAB to the last focusable item + lastElement = elementsInOrder.pop(); + await t.context.session + .findElement(By.css(lastElement)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + // Confirm focus moves through remaining items + for (let index = elementsInOrder.length - 1; index >= 0; index--) { + let itemSelector = elementsInOrder[index]; + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should reach element: ' + itemSelector + ); + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + } } +); + +ariaTest( + 'DOWN ARROW moves focus between headers', + exampleFile, + 'key-down-arrow', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + // Confirm focus moves through remaining items + for (let index = 0; index < ex.buttonsInOrder.length - 1; index++) { + let itemSelector = ex.buttonsInOrder[index]; + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.ARROW_DOWN); + + t.true( + await focusMatchesElement(t, ex.buttonsInOrder[index + 1]), + 'Focus should reach element: ' + ex.buttonsInOrder[index + 1] + ); + } - // Focus should wrap to last item - let itemSelector = ex.buttonsInOrder[0]; - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.ARROW_UP); - - t.true( - await focusMatchesElement(t, ex.buttonsInOrder[ex.buttonsInOrder.length - 1]), - 'Focus should reach element: ' + ex.buttonsInOrder[ex.buttonsInOrder.length - 1] - ); - -}); - -ariaTest('HOME key will always move focus to first button', exampleFile, 'key-home', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const lastIndex = ex.buttonsInOrder.length - 1; - - // Confirm focus moves through remaining items - for (let index = 0; index <= lastIndex; index++) { - - let itemSelector = ex.buttonsInOrder[index]; - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.HOME); + // Focus should wrap to first item + let itemSelector = ex.buttonsInOrder[ex.buttonsInOrder.length - 1]; + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.ARROW_DOWN); t.true( await focusMatchesElement(t, ex.buttonsInOrder[0]), 'Focus should reach element: ' + ex.buttonsInOrder[0] ); } +); + +ariaTest( + 'UP ARROW moves focus between headers', + exampleFile, + 'key-up-arrow', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + // Confirm focus moves through remaining items + for (let index = ex.buttonsInOrder.length - 1; index > 0; index--) { + let itemSelector = ex.buttonsInOrder[index]; + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.ARROW_UP); + + t.true( + await focusMatchesElement(t, ex.buttonsInOrder[index - 1]), + 'Focus should reach element: ' + ex.buttonsInOrder[index - 1] + ); + } -}); - -ariaTest('END key will always move focus to last button', exampleFile, 'key-end', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const lastIndex = ex.buttonsInOrder.length - 1; - - // Confirm focus moves through remaining items - for (let index = lastIndex; index >= 0; index--) { - - let itemSelector = ex.buttonsInOrder[index]; - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.END); + // Focus should wrap to last item + let itemSelector = ex.buttonsInOrder[0]; + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.ARROW_UP); t.true( - await focusMatchesElement(t, ex.buttonsInOrder[lastIndex]), - 'Focus should reach element: ' + ex.buttonsInOrder[lastIndex] + await focusMatchesElement( + t, + ex.buttonsInOrder[ex.buttonsInOrder.length - 1] + ), + 'Focus should reach element: ' + + ex.buttonsInOrder[ex.buttonsInOrder.length - 1] ); } -}); +); + +ariaTest( + 'HOME key will always move focus to first button', + exampleFile, + 'key-home', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const lastIndex = ex.buttonsInOrder.length - 1; + + // Confirm focus moves through remaining items + for (let index = 0; index <= lastIndex; index++) { + let itemSelector = ex.buttonsInOrder[index]; + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.HOME); + + t.true( + await focusMatchesElement(t, ex.buttonsInOrder[0]), + 'Focus should reach element: ' + ex.buttonsInOrder[0] + ); + } + } +); + +ariaTest( + 'END key will always move focus to last button', + exampleFile, + 'key-end', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const lastIndex = ex.buttonsInOrder.length - 1; + + // Confirm focus moves through remaining items + for (let index = lastIndex; index >= 0; index--) { + let itemSelector = ex.buttonsInOrder[index]; + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.END); + + t.true( + await focusMatchesElement(t, ex.buttonsInOrder[lastIndex]), + 'Focus should reach element: ' + ex.buttonsInOrder[lastIndex] + ); + } + } +); diff --git a/test/tests/alert_alert.js b/test/tests/alert_alert.js index 00c5f3c86b..728324db4e 100644 --- a/test/tests/alert_alert.js +++ b/test/tests/alert_alert.js @@ -7,23 +7,33 @@ const exampleFile = 'alert/alert.html'; const ex = { buttonSelector: '#alert-trigger', - alertSelector: '#ex1 [role="alert"]' + alertSelector: '#ex1 [role="alert"]', }; // Attributes -ariaTest('role="alert" on alert element', exampleFile, 'alert-role', async (t) => { - - t.false( - await t.context.session.findElement(By.css(ex.alertSelector)).isDisplayed(), - '[role="alert"] element found and should not be displayed on page load' - ); +ariaTest( + 'role="alert" on alert element', + exampleFile, + 'alert-role', + async (t) => { + t.false( + await t.context.session + .findElement(By.css(ex.alertSelector)) + .isDisplayed(), + '[role="alert"] element found and should not be displayed on page load' + ); - let alertButton = await t.context.session.findElement(By.css(ex.buttonSelector)); - await alertButton.click(); + let alertButton = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); + await alertButton.click(); - t.true( - await t.context.session.findElement(By.css(ex.alertSelector)).isDisplayed(), - '[role="alert"] element found and is displayed after triggered' - ); -}); + t.true( + await t.context.session + .findElement(By.css(ex.alertSelector)) + .isDisplayed(), + '[role="alert"] element found and is displayed after triggered' + ); + } +); diff --git a/test/tests/breadcrumb_index.js b/test/tests/breadcrumb_index.js index f54bb4ab37..598f5c991e 100644 --- a/test/tests/breadcrumb_index.js +++ b/test/tests/breadcrumb_index.js @@ -7,31 +7,44 @@ const assertAriaLabelExists = require('../util/assertAriaLabelExists'); const exampleFile = 'breadcrumb/index.html'; const ex = { - breadcrumbSelector: '#ex1 nav' + breadcrumbSelector: '#ex1 nav', }; // Attributes -ariaTest('aria-label attribute on nav element', exampleFile, 'aria-label', async (t) => { +ariaTest( + 'aria-label attribute on nav element', + exampleFile, + 'aria-label', + async (t) => { await assertAriaLabelExists(t, ex.breadcrumbSelector); -}); - -ariaTest('aria-current element should exist on relevant link', exampleFile, 'aria-current', async (t) => { - - let navElement = await t.context.session.findElement(By.css(ex.breadcrumbSelector)); - let currentElement = await t.context.queryElements(t, '[aria-current]', navElement); - - t.is( - currentElement.length, - 1, - 'Only one element in the nav should have attribute "aria-current"' - ); - - t.is( - await currentElement[0].getTagName(), - 'a', - 'attribute "aria-current" should be found on a link' - ); -}); - - + } +); + +ariaTest( + 'aria-current element should exist on relevant link', + exampleFile, + 'aria-current', + async (t) => { + let navElement = await t.context.session.findElement( + By.css(ex.breadcrumbSelector) + ); + let currentElement = await t.context.queryElements( + t, + '[aria-current]', + navElement + ); + + t.is( + currentElement.length, + 1, + 'Only one element in the nav should have attribute "aria-current"' + ); + + t.is( + await currentElement[0].getTagName(), + 'a', + 'attribute "aria-current" should be found on a link' + ); + } +); diff --git a/test/tests/button_button.js b/test/tests/button_button.js index fa687c1247..502869ac6b 100644 --- a/test/tests/button_button.js +++ b/test/tests/button_button.js @@ -9,192 +9,262 @@ const ex = { buttons: [ { id: 'action', - tag: 'div' + tag: 'div', }, { id: 'toggle', - tag: 'a' - } + tag: 'a', + }, ], - buttonSelector: '#example [role="button"]' + buttonSelector: '#example [role="button"]', }; // Attributes -ariaTest('Example elements should have role="button" set', exampleFile, 'button-role', async (t) => { - - for (let button of ex.buttons) { - let buttonEl = await t.context.session.findElement(By.id(button.id)); +ariaTest( + 'Example elements should have role="button" set', + exampleFile, + 'button-role', + async (t) => { + for (let button of ex.buttons) { + let buttonEl = await t.context.session.findElement(By.id(button.id)); + + t.is( + await buttonEl.getAttribute('role'), + 'button', + 'Role on button example #' + button.id + ' should have role "button"' + ); + + t.is( + await buttonEl.getTagName(), + button.tag, + 'Tag on button example #' + + button.id + + ' should have tag: ' + + button.tag + ); + } + } +); + +ariaTest( + 'Button examples should have tabindex="0"', + exampleFile, + 'button-tabindex', + async (t) => { + for (let button of ex.buttons) { + let buttonEl = await t.context.session.findElement(By.id(button.id)); + + t.is( + await buttonEl.getAttribute('tabindex'), + '0', + 'tabindex should be set to "0" on button example: #' + button.id + ); + } + } +); + +ariaTest( + '"aria-pressed" reflects button state', + exampleFile, + 'button-aria-pressed', + async (t) => { + let toggleButtonSelector = '#' + ex.buttons[1].id; + + let ariaPressedExists = await t.context.session.executeScript( + async function () { + const selector = arguments[0]; + let el = document.querySelector(selector); + return el.hasAttribute('aria-pressed'); + }, + toggleButtonSelector + ); t.is( - await buttonEl.getAttribute('role'), - 'button', - 'Role on button example #' + button.id + ' should have role "button"' + ariaPressedExists, + true, + 'aria-pressed attribute should exist on example: ' + toggleButtonSelector ); + let toggleButtonEl = await t.context.session.findElement( + By.css(toggleButtonSelector) + ); t.is( - await buttonEl.getTagName(), - button.tag, - 'Tag on button example #' + button.id + ' should have tag: ' + button.tag + await toggleButtonEl.getAttribute('aria-pressed'), + 'false', + 'aria-pressed should be set to "false" by default on example: #' + + toggleButtonSelector ); - } -}); -ariaTest('Button examples should have tabindex="0"', exampleFile, 'button-tabindex', async (t) => { - - for (let button of ex.buttons) { - let buttonEl = await t.context.session.findElement(By.id(button.id)); + // Click and wait for change of 'aria-pressed' + await toggleButtonEl.click(); + await t.context.session.wait( + async function () { + return toggleButtonEl.getAttribute('aria-pressed') !== 'true'; + }, + t.context.waitTime, + 'Timeout waiting for aria-pressed to change from "true"' + ); t.is( - await buttonEl.getAttribute('tabindex'), - '0', - 'tabindex should be set to "0" on button example: #' + button.id + await toggleButtonEl.getAttribute('aria-pressed'), + 'true', + 'aria-pressed should be set to "false" after clicking on example: #' + + toggleButtonSelector ); } -}); - -ariaTest('"aria-pressed" reflects button state', exampleFile, 'button-aria-pressed', async (t) => { - - let toggleButtonSelector = '#' + ex.buttons[1].id; - - let ariaPressedExists = await t.context.session.executeScript(async function () { - const selector = arguments[0]; - let el = document.querySelector(selector); - return el.hasAttribute('aria-pressed'); - }, toggleButtonSelector); - - t.is( - ariaPressedExists, - true, - 'aria-pressed attribute should exist on example: ' + toggleButtonSelector - ); - - let toggleButtonEl = await t.context.session.findElement(By.css(toggleButtonSelector)); - t.is( - await toggleButtonEl.getAttribute('aria-pressed'), - 'false', - 'aria-pressed should be set to "false" by default on example: #' + toggleButtonSelector - ); - - // Click and wait for change of 'aria-pressed' - await toggleButtonEl.click(); - await t.context.session.wait(async function () { - return toggleButtonEl.getAttribute('aria-pressed') !== 'true'; - }, t.context.waitTime, 'Timeout waiting for aria-pressed to change from "true"'); - - t.is( - await toggleButtonEl.getAttribute('aria-pressed'), - 'true', - 'aria-pressed should be set to "false" after clicking on example: #' + toggleButtonSelector - ); -}); +); ariaTest('key ENTER activates button', exampleFile, 'key-enter', async (t) => { - let toggleButtonSelector = '#' + ex.buttons[1].id; - let toggleButtonEl = await t.context.session.findElement(By.css(toggleButtonSelector)); + let toggleButtonEl = await t.context.session.findElement( + By.css(toggleButtonSelector) + ); // Send ENTER and wait for change of 'aria-pressed' await toggleButtonEl.sendKeys(Key.ENTER); - await t.context.session.wait(async function () { - return toggleButtonEl.getAttribute('aria-pressed') !== 'true'; - }, t.context.waitTime, 'Timeout waiting for aria-pressed to change from "true"'); + await t.context.session.wait( + async function () { + return toggleButtonEl.getAttribute('aria-pressed') !== 'true'; + }, + t.context.waitTime, + 'Timeout waiting for aria-pressed to change from "true"' + ); t.is( await toggleButtonEl.getAttribute('aria-pressed'), 'true', - 'aria-pressed should be set to "false" after sending enter to example: ' + toggleButtonSelector + 'aria-pressed should be set to "false" after sending enter to example: ' + + toggleButtonSelector ); // Send ENTER again and wait for change of 'aria-pressed' await toggleButtonEl.sendKeys(Key.ENTER); - await t.context.session.wait(async function () { - return toggleButtonEl.getAttribute('aria-pressed') !== 'false'; - }, t.context.waitTime, 'Timeout waiting for aria-pressed to change from "false"'); + await t.context.session.wait( + async function () { + return toggleButtonEl.getAttribute('aria-pressed') !== 'false'; + }, + t.context.waitTime, + 'Timeout waiting for aria-pressed to change from "false"' + ); t.is( await toggleButtonEl.getAttribute('aria-pressed'), 'false', - 'aria-pressed should be set to "false" after sending enter to example: ' + toggleButtonSelector + 'aria-pressed should be set to "false" after sending enter to example: ' + + toggleButtonSelector ); let actionButtonSelector = '#' + ex.buttons[0].id; - let actionButtonEl = await t.context.session.findElement(By.css(actionButtonSelector)); + let actionButtonEl = await t.context.session.findElement( + By.css(actionButtonSelector) + ); let oldText = await actionButtonEl.getText(); let newText = oldText + ' - Printed!'; - await t.context.session.executeScript(function () { - let [selector, newText] = arguments; - window.print = function () { - document.querySelector(selector).innerText = newText; - }; - }, actionButtonSelector, newText); + await t.context.session.executeScript( + function () { + let [selector, newText] = arguments; + window.print = function () { + document.querySelector(selector).innerText = newText; + }; + }, + actionButtonSelector, + newText + ); // Send ENTER and wait for change of 'aria-pressed' await actionButtonEl.sendKeys(Key.ENTER); - await t.context.session.wait(async function () { - return actionButtonEl.getText('') !== oldText; - }, t.context.waitTime, 'window.print was not executed'); + await t.context.session.wait( + async function () { + return actionButtonEl.getText('') !== oldText; + }, + t.context.waitTime, + 'window.print was not executed' + ); t.is( await actionButtonEl.getText(), newText, - 'window.print should have been triggered after sending ENTER to example: ' + actionButtonSelector + 'window.print should have been triggered after sending ENTER to example: ' + + actionButtonSelector ); }); ariaTest('key SPACE activates button', exampleFile, 'key-space', async (t) => { - let toggleButtonSelector = '#' + ex.buttons[1].id; - let toggleButtonEl = await t.context.session.findElement(By.css(toggleButtonSelector)); + let toggleButtonEl = await t.context.session.findElement( + By.css(toggleButtonSelector) + ); // Send SPACE and wait for change of 'aria-pressed' await toggleButtonEl.sendKeys(Key.SPACE); - await t.context.session.wait(async function () { - return toggleButtonEl.getAttribute('aria-pressed') !== 'true'; - }, t.context.waitTime, 'Timeout waiting for aria-pressed to change from "true"'); + await t.context.session.wait( + async function () { + return toggleButtonEl.getAttribute('aria-pressed') !== 'true'; + }, + t.context.waitTime, + 'Timeout waiting for aria-pressed to change from "true"' + ); t.is( await toggleButtonEl.getAttribute('aria-pressed'), 'true', - 'aria-pressed should be set to "false" after sending space to example: ' + toggleButtonSelector + 'aria-pressed should be set to "false" after sending space to example: ' + + toggleButtonSelector ); // Send SPACE again and wait for change of 'aria-pressed' await toggleButtonEl.sendKeys(Key.SPACE); - await t.context.session.wait(async function () { - return toggleButtonEl.getAttribute('aria-pressed') !== 'false'; - }, t.context.waitTime, 'Timeout waiting for aria-pressed to change from "false"'); + await t.context.session.wait( + async function () { + return toggleButtonEl.getAttribute('aria-pressed') !== 'false'; + }, + t.context.waitTime, + 'Timeout waiting for aria-pressed to change from "false"' + ); t.is( await toggleButtonEl.getAttribute('aria-pressed'), 'false', - 'aria-pressed should be set to "false" after sending space to example: ' + toggleButtonSelector + 'aria-pressed should be set to "false" after sending space to example: ' + + toggleButtonSelector ); let actionButtonSelector = '#' + ex.buttons[0].id; - let actionButtonEl = await t.context.session.findElement(By.css(actionButtonSelector)); + let actionButtonEl = await t.context.session.findElement( + By.css(actionButtonSelector) + ); let oldText = await actionButtonEl.getText(); let newText = oldText + ' - Printed!'; - await t.context.session.executeScript(function () { - let [selector, newText] = arguments; - window.print = function () { - document.querySelector(selector).innerText = newText; - }; - }, actionButtonSelector, newText); + await t.context.session.executeScript( + function () { + let [selector, newText] = arguments; + window.print = function () { + document.querySelector(selector).innerText = newText; + }; + }, + actionButtonSelector, + newText + ); // Send SPACE and wait for change of 'aria-pressed' await actionButtonEl.sendKeys(Key.SPACE); - await t.context.session.wait(async function () { - return actionButtonEl.getText('') !== oldText; - }, t.context.waitTime, 'window.print was not executed'); + await t.context.session.wait( + async function () { + return actionButtonEl.getText('') !== oldText; + }, + t.context.waitTime, + 'window.print was not executed' + ); t.is( await actionButtonEl.getText(), newText, - 'window.print should have been triggered after sending SPACE to example: ' + actionButtonSelector + 'window.print should have been triggered after sending SPACE to example: ' + + actionButtonSelector ); }); diff --git a/test/tests/carousel_carousel-1-prev-next.js b/test/tests/carousel_carousel-1-prev-next.js index 5629c964ae..2654a4fee3 100644 --- a/test/tests/carousel_carousel-1-prev-next.js +++ b/test/tests/carousel_carousel-1-prev-next.js @@ -23,215 +23,365 @@ const ex = { '#ex1 .previous', '#ex1 .next', '#ex1 .active .carousel-image a', - '#ex1 .active .carousel-caption a' + '#ex1 .active .carousel-caption a', ], - activeCarouselItem: '#ex1 .active' + activeCarouselItem: '#ex1 .active', }; - // Attributes -ariaTest('section element used to contain slider', exampleFile, 'carousel-region-role', async (t) => { - - // This test primarily tests that the ex.landmarkSelector points to a `section` element - const landmarkEl = await t.context.session.findElement(By.css(ex.landmarkSelector)); - t.is( - await landmarkEl.getTagName(), - 'section', - ex.landmarkSelector + ' selector should select `section` element' - ); -}); - -ariaTest('section has aria-roledescription set to carousel', exampleFile, 'carousel-region-aria-roledescription', async (t) => { - - // check the aria-roledescrption set to carousel - await assertAttributeValues(t, ex.landmarkSelector, 'aria-roledescription', 'carousel'); -}); - -ariaTest('section has aria-label', exampleFile, 'carousel-region-aria-label', async (t) => { - - await assertAriaLabelExists(t, ex.landmarkSelector); -}); - -ariaTest('slide container have aria-live initially set to off', exampleFile, 'carousel-aria-live', async (t) => { - - // On page load, `aria-level` is `off` - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'off'); - - // Focus on the widget, and aria-live should change to 'polite' - await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.ENTER); - - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'polite'); - - // Move focus off the widget, and the aria-selected should change to 'off' again - await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'off'); - - // Click the pause button, and the aria-selected should change to 'polite' again - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).click(); - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'polite'); -}); - -ariaTest('pause, previous and next buttons have aria-label', exampleFile, 'carousel-button-aria-label', async (t) => { +ariaTest( + 'section element used to contain slider', + exampleFile, + 'carousel-region-role', + async (t) => { + // This test primarily tests that the ex.landmarkSelector points to a `section` element + const landmarkEl = await t.context.session.findElement( + By.css(ex.landmarkSelector) + ); + t.is( + await landmarkEl.getTagName(), + 'section', + ex.landmarkSelector + ' selector should select `section` element' + ); + } +); + +ariaTest( + 'section has aria-roledescription set to carousel', + exampleFile, + 'carousel-region-aria-roledescription', + async (t) => { + // check the aria-roledescrption set to carousel + await assertAttributeValues( + t, + ex.landmarkSelector, + 'aria-roledescription', + 'carousel' + ); + } +); + +ariaTest( + 'section has aria-label', + exampleFile, + 'carousel-region-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.landmarkSelector); + } +); + +ariaTest( + 'slide container have aria-live initially set to off', + exampleFile, + 'carousel-aria-live', + async (t) => { + // On page load, `aria-level` is `off` + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'off' + ); + + // Focus on the widget, and aria-live should change to 'polite' + await t.context.session + .findElement(By.css(ex.nextButtonSelector)) + .sendKeys(Key.ENTER); + + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'polite' + ); + + // Move focus off the widget, and the aria-selected should change to 'off' again + await t.context.session + .findElement(By.css(ex.nextButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'off' + ); + + // Click the pause button, and the aria-selected should change to 'polite' again + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .click(); + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'polite' + ); + } +); + +ariaTest( + 'pause, previous and next buttons have aria-label', + exampleFile, + 'carousel-button-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.buttonSelector); -}); - -ariaTest('previous and next buttons have aria-controls', exampleFile, 'carousel-button-aria-controls', async (t) => { + } +); + +ariaTest( + 'previous and next buttons have aria-controls', + exampleFile, + 'carousel-button-aria-controls', + async (t) => { await assertAriaControls(t, ex.previousButtonSelector); - await assertAriaControls(t, ex.nextButtonSelector); -}); - -ariaTest('slides have role group', exampleFile, 'carousel-group-role', async (t) => { + await assertAriaControls(t, ex.nextButtonSelector); + } +); + +ariaTest( + 'slides have role group', + exampleFile, + 'carousel-group-role', + async (t) => { await assertAriaRoles(t, 'myCarousel', 'group', 6, 'div'); -}); - -ariaTest('slides have aria-label', exampleFile, 'carousel-group-aria-label', async (t) => { + } +); + +ariaTest( + 'slides have aria-label', + exampleFile, + 'carousel-group-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.slideSelector); -}); - -ariaTest('slides have aria-roledescription set to slide', exampleFile, 'carousel-group-aria-roledescription', async (t) => { - - // check the aria-roledescrption set to carousel - await assertAttributeValues(t, ex.slideSelector, 'aria-roledescription', 'slide'); -}); + } +); + +ariaTest( + 'slides have aria-roledescription set to slide', + exampleFile, + 'carousel-group-aria-roledescription', + async (t) => { + // check the aria-roledescrption set to carousel + await assertAttributeValues( + t, + ex.slideSelector, + 'aria-roledescription', + 'slide' + ); + } +); // Keyboard interaction -ariaTest('TAB moves key through buttons', exampleFile, 'carousel-key-tab', async (t) => { - - await assertTabOrder(t, ex.allFocusableItems); -}); - -ariaTest('ENTER pause and start carousel motion', exampleFile, 'carousel-enter-or-space-toggle', async (t) => { - - let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.ENTER); - // Move focus from widget - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - - let compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement === newActiveElement; - }, t.context.WaitTime); - - t.true( - compareWithNextElement, - 'The active elements should stay the same when the pause button has been sent ENTER' - ); - - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.ENTER); - // Move focus from widget - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - - compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement !== newActiveElement; - }, t.context.WaitTime); - - t.true( - compareWithNextElement, - 'The active elements should change when the play button has been sent ENTER' - ); - -}); - - -ariaTest('SPACE pause and start carousel motion', exampleFile, 'carousel-enter-or-space-toggle', async (t) => { - - let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.SPACE); - // Move focus from widget - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - - let compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement === newActiveElement; - }, t.context.WaitTime); - - t.true( - compareWithNextElement, - 'The active elements should stay the same when the pause button has been sent SPACE' - ); - - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.SPACE); - // Move focus from widget - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - - compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement !== newActiveElement; - }, t.context.WaitTime); - - t.true( - compareWithNextElement, - 'The active elements should change when the play button has been sent SPACE' - ); -}); - - -ariaTest('SPACE on previous and next', exampleFile, 'carousel-key-enter-or-space-move', async (t) => { - - let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)) - .getAttribute('aria-label'); - - await t.context.session.findElement(By.css(ex.previousButtonSelector)).sendKeys(Key.SPACE); - - let compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)) +ariaTest( + 'TAB moves key through buttons', + exampleFile, + 'carousel-key-tab', + async (t) => { + await assertTabOrder(t, ex.allFocusableItems); + } +); + +ariaTest( + 'ENTER pause and start carousel motion', + exampleFile, + 'carousel-enter-or-space-toggle', + async (t) => { + let activeElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) .getAttribute('aria-label'); - return activeElement !== newActiveElement; - }, t.context.WaitTime); - t.true( - compareWithNextElement, - 'After sending SPACE to previous button, the carousel should show a different element' - ); - - await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.SPACE); - - compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)) + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.ENTER); + // Move focus from widget + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + let compareWithNextElement = await t.context.session.wait( + async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement === newActiveElement; + }, + t.context.WaitTime + ); + + t.true( + compareWithNextElement, + 'The active elements should stay the same when the pause button has been sent ENTER' + ); + + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.ENTER); + // Move focus from widget + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + compareWithNextElement = await t.context.session.wait(async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement !== newActiveElement; + }, t.context.WaitTime); + + t.true( + compareWithNextElement, + 'The active elements should change when the play button has been sent ENTER' + ); + } +); + +ariaTest( + 'SPACE pause and start carousel motion', + exampleFile, + 'carousel-enter-or-space-toggle', + async (t) => { + let activeElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) .getAttribute('aria-label'); - return activeElement === newActiveElement; - }, t.context.WaitTime); - - t.true( - compareWithNextElement, - 'After sending SPACE to previous button then SPACE to next button, the carousel should show the first carousel item' - ); -}); - -ariaTest('ENTER on previous and next', exampleFile, 'carousel-key-enter-or-space-move', async (t) => { - - let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)) - .getAttribute('aria-label'); - await t.context.session.findElement(By.css(ex.previousButtonSelector)).sendKeys(Key.ENTER); - - let compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)) + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.SPACE); + // Move focus from widget + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + let compareWithNextElement = await t.context.session.wait( + async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement === newActiveElement; + }, + t.context.WaitTime + ); + + t.true( + compareWithNextElement, + 'The active elements should stay the same when the pause button has been sent SPACE' + ); + + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.SPACE); + // Move focus from widget + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + compareWithNextElement = await t.context.session.wait(async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement !== newActiveElement; + }, t.context.WaitTime); + + t.true( + compareWithNextElement, + 'The active elements should change when the play button has been sent SPACE' + ); + } +); + +ariaTest( + 'SPACE on previous and next', + exampleFile, + 'carousel-key-enter-or-space-move', + async (t) => { + let activeElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) .getAttribute('aria-label'); - return activeElement !== newActiveElement; - }, t.context.WaitTime); - - t.true( - compareWithNextElement, - 'After sending ENTER to previous button, the carousel should show a different element' - ); - await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.ENTER); - - compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)) + await t.context.session + .findElement(By.css(ex.previousButtonSelector)) + .sendKeys(Key.SPACE); + + let compareWithNextElement = await t.context.session.wait( + async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement !== newActiveElement; + }, + t.context.WaitTime + ); + + t.true( + compareWithNextElement, + 'After sending SPACE to previous button, the carousel should show a different element' + ); + + await t.context.session + .findElement(By.css(ex.nextButtonSelector)) + .sendKeys(Key.SPACE); + + compareWithNextElement = await t.context.session.wait(async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement === newActiveElement; + }, t.context.WaitTime); + + t.true( + compareWithNextElement, + 'After sending SPACE to previous button then SPACE to next button, the carousel should show the first carousel item' + ); + } +); + +ariaTest( + 'ENTER on previous and next', + exampleFile, + 'carousel-key-enter-or-space-move', + async (t) => { + let activeElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) .getAttribute('aria-label'); - return activeElement === newActiveElement; - }, t.context.WaitTime); - - t.true( - compareWithNextElement, - 'After sending ENTER to previous button then ENTER to next button, the carousel should show the first carousel item' - ); -}); + + await t.context.session + .findElement(By.css(ex.previousButtonSelector)) + .sendKeys(Key.ENTER); + + let compareWithNextElement = await t.context.session.wait( + async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement !== newActiveElement; + }, + t.context.WaitTime + ); + + t.true( + compareWithNextElement, + 'After sending ENTER to previous button, the carousel should show a different element' + ); + + await t.context.session + .findElement(By.css(ex.nextButtonSelector)) + .sendKeys(Key.ENTER); + + compareWithNextElement = await t.context.session.wait(async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement === newActiveElement; + }, t.context.WaitTime); + + t.true( + compareWithNextElement, + 'After sending ENTER to previous button then ENTER to next button, the carousel should show the first carousel item' + ); + } +); diff --git a/test/tests/carousel_carousel-2-tablist.js b/test/tests/carousel_carousel-2-tablist.js index 2a257abf13..51c4909047 100644 --- a/test/tests/carousel_carousel-2-tablist.js +++ b/test/tests/carousel_carousel-2-tablist.js @@ -27,17 +27,17 @@ const ex = { ['#carousel-tab-3', '#carousel-image-3'], ['#carousel-tab-4', '#carousel-image-4'], ['#carousel-tab-5', '#carousel-image-5'], - ['#carousel-tab-6', '#carousel-image-6'] + ['#carousel-tab-6', '#carousel-image-6'], ], allFocusableItems: [ '#ex1 button:first-of-type', '#ex1 [role="tab"][aria-selected=true]', '#ex1 .active .carousel-image a', - '#ex1 .active .carousel-caption a' + '#ex1 .active .carousel-caption a', ], rotationLabelPlaying: 'Stop automatic slide show', rotationLabelPaused: 'Start automatic slide show', - activeCarouselItem: '#ex1 .active' + activeCarouselItem: '#ex1 .active', }; const openTabAtIndex = async function (t, index) { @@ -46,127 +46,210 @@ const openTabAtIndex = async function (t, index) { }; const waitAndCheckFocus = async function (t, selector, index) { - return t.context.session.wait(async function () { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - let items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); - }, t.context.waitTime, 'Timeout waiting for document.activeElement to become item at index ' + index + ' of elements selected by: ' + selector); + return t.context.session.wait( + async function () { + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + let items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); + }, + t.context.waitTime, + 'Timeout waiting for document.activeElement to become item at index ' + + index + + ' of elements selected by: ' + + selector + ); }; const waitAndCheckAriaSelected = async function (t, index) { - return t.context.session.wait(async function () { - const tabs = await t.context.queryElements(t, ex.tabSelector); - return (await tabs[index].getAttribute('aria-selected')) === 'true'; - }, t.context.waitTime, 'Timeout waiting for aria-selected to be set to true.'); + return t.context.session.wait( + async function () { + const tabs = await t.context.queryElements(t, ex.tabSelector); + return (await tabs[index].getAttribute('aria-selected')) === 'true'; + }, + t.context.waitTime, + 'Timeout waiting for aria-selected to be set to true.' + ); }; // Attributes -ariaTest('rotation button has aria-label that is updated based on pause state', exampleFile, 'rotation-aria-label', async (t) => { - t.plan(4); - await assertAriaLabelExists(t, ex.rotationSelector); - await assertAttributeValues(t, ex.rotationSelector, 'aria-label', ex.rotationLabelPlaying); - - let rotationButtonEl = await t.context.session.findElement(By.css(ex.rotationSelector)); - - // Send SPACE key and wait for change of 'aria-label' - await rotationButtonEl.sendKeys(Key.SPACE); - await t.context.session.wait(async function () { - return rotationButtonEl.getAttribute('aria-label') !== ex.rotationLabelPlaying; - }, t.context.waitTime, 'Timeout waiting for rotation button\'s aria-label to change'); - - await assertAttributeValues(t, ex.rotationSelector, 'aria-label', ex.rotationLabelPaused); - - // Send ENTER key and wait for change of 'aria-label' - await rotationButtonEl.sendKeys(Key.ENTER); - await t.context.session.wait(async function () { - return rotationButtonEl.getAttribute('aria-label') !== ex.rotationLabelPaused; - }, t.context.waitTime, 'Timeout waiting for rotation button\'s aria-label to change'); - - await assertAttributeValues(t, ex.rotationSelector, 'aria-label', ex.rotationLabelPlaying); - - -}); - -ariaTest('role="tablist" on div element', exampleFile, 'tablist-role', async (t) => { - t.plan(1); - await assertAriaRoles(t, 'ex1', 'tablist', '1', 'div'); -}); +ariaTest( + 'rotation button has aria-label that is updated based on pause state', + exampleFile, + 'rotation-aria-label', + async (t) => { + t.plan(4); + await assertAriaLabelExists(t, ex.rotationSelector); + await assertAttributeValues( + t, + ex.rotationSelector, + 'aria-label', + ex.rotationLabelPlaying + ); -ariaTest('"aria-label" attribute on role="tablist"', exampleFile, 'tablist-aria-label', async (t) => { - t.plan(1); - await assertAriaLabelExists(t, ex.tablistSelector); -}); + let rotationButtonEl = await t.context.session.findElement( + By.css(ex.rotationSelector) + ); -ariaTest('role="tab" on button elements', exampleFile, 'tab-role', async (t) => { - t.plan(1); - await assertAriaRoles(t, 'ex1', 'tab', ex.tabCount, 'button'); -}); + // Send SPACE key and wait for change of 'aria-label' + await rotationButtonEl.sendKeys(Key.SPACE); + await t.context.session.wait( + async function () { + return ( + rotationButtonEl.getAttribute('aria-label') !== + ex.rotationLabelPlaying + ); + }, + t.context.waitTime, + "Timeout waiting for rotation button's aria-label to change" + ); -ariaTest('"aria-label" attribute on role="tab"', exampleFile, 'tab-aria-label', async (t) => { - t.plan(1); - await assertAriaLabelExists(t, ex.tabSelector); -}); + await assertAttributeValues( + t, + ex.rotationSelector, + 'aria-label', + ex.rotationLabelPaused + ); -ariaTest('"aria-selected" set on role="tab"', exampleFile, 'tab-aria-selected', async (t) => { - t.plan(2 * ex.tabCount * ex.tabCount); + // Send ENTER key and wait for change of 'aria-label' + await rotationButtonEl.sendKeys(Key.ENTER); + await t.context.session.wait( + async function () { + return ( + rotationButtonEl.getAttribute('aria-label') !== ex.rotationLabelPaused + ); + }, + t.context.waitTime, + "Timeout waiting for rotation button's aria-label to change" + ); - let tabs = await t.context.queryElements(t, ex.tabSelector); - let tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + await assertAttributeValues( + t, + ex.rotationSelector, + 'aria-label', + ex.rotationLabelPlaying + ); + } +); + +ariaTest( + 'role="tablist" on div element', + exampleFile, + 'tablist-role', + async (t) => { + t.plan(1); + await assertAriaRoles(t, 'ex1', 'tablist', '1', 'div'); + } +); + +ariaTest( + '"aria-label" attribute on role="tablist"', + exampleFile, + 'tablist-aria-label', + async (t) => { + t.plan(1); + await assertAriaLabelExists(t, ex.tablistSelector); + } +); + +ariaTest( + 'role="tab" on button elements', + exampleFile, + 'tab-role', + async (t) => { + t.plan(1); + await assertAriaRoles(t, 'ex1', 'tab', ex.tabCount, 'button'); + } +); + +ariaTest( + '"aria-label" attribute on role="tab"', + exampleFile, + 'tab-aria-label', + async (t) => { + t.plan(1); + await assertAriaLabelExists(t, ex.tabSelector); + } +); - for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { +ariaTest( + '"aria-selected" set on role="tab"', + exampleFile, + 'tab-aria-selected', + async (t) => { + t.plan(2 * ex.tabCount * ex.tabCount); - // Open the tab - await openTabAtIndex(t, selectedEl); + let tabs = await t.context.queryElements(t, ex.tabSelector); + let tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let el = 0; el < tabs.length; el++) { + for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { + // Open the tab + await openTabAtIndex(t, selectedEl); - // test only one element has aria-selected="true" - let selected = el === selectedEl ? 'true' : 'false'; - t.is( - await tabs[el].getAttribute('aria-selected'), - selected, - 'Tab at index ' + selectedEl + ' is selected, therefore, tab at index ' + - el + ' should have aria-selected="' + selected + '"' - ); + for (let el = 0; el < tabs.length; el++) { + // test only one element has aria-selected="true" + let selected = el === selectedEl ? 'true' : 'false'; + t.is( + await tabs[el].getAttribute('aria-selected'), + selected, + 'Tab at index ' + + selectedEl + + ' is selected, therefore, tab at index ' + + el + + ' should have aria-selected="' + + selected + + '"' + ); - // test only the appropriate tabpanel element is visible - let tabpanelVisible = el === selectedEl; - t.is( - await tabpanels[el].isDisplayed(), - tabpanelVisible, - 'Tab at index ' + selectedEl + ' is selected, therefore, only the tabpanel at ' + - 'index ' + selectedEl + ' should be displayed' - ); + // test only the appropriate tabpanel element is visible + let tabpanelVisible = el === selectedEl; + t.is( + await tabpanels[el].isDisplayed(), + tabpanelVisible, + 'Tab at index ' + + selectedEl + + ' is selected, therefore, only the tabpanel at ' + + 'index ' + + selectedEl + + ' should be displayed' + ); + } } } -}); - +); ariaTest('"tabindex" on role="tab"', exampleFile, 'tab-tabindex', async (t) => { t.plan(ex.tabCount * ex.tabCount); let tabs = await t.context.queryElements(t, ex.tabSelector); for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { - // Open the tab await openTabAtIndex(t, selectedEl); for (let el = 0; el < tabs.length; el++) { - // The open tab should have tabindex of 0 if (el === selectedEl) { - const tabindexExists = await t.context.session.executeScript(async function () { - const [selector, el] = arguments; - let tabs = document.querySelectorAll(selector); - return tabs[el].hasAttribute('tabindex'); - }, ex.tabSelector, el); + const tabindexExists = await t.context.session.executeScript( + async function () { + const [selector, el] = arguments; + let tabs = document.querySelectorAll(selector); + return tabs[el].hasAttribute('tabindex'); + }, + ex.tabSelector, + el + ); t.false( tabindexExists, - 'Tab at index ' + selectedEl + ' is selected, therefore, that tab should not ' + + 'Tab at index ' + + selectedEl + + ' is selected, therefore, that tab should not ' + 'have the "tabindex" attribute' ); } @@ -176,305 +259,458 @@ ariaTest('"tabindex" on role="tab"', exampleFile, 'tab-tabindex', async (t) => { t.is( await tabs[el].getAttribute('tabindex'), '-1', - 'Tab at index ' + selectedEl + ' is selected, therefore, tab at index ' + - el + ' should have tabindex="-1"' + 'Tab at index ' + + selectedEl + + ' is selected, therefore, tab at index ' + + el + + ' should have tabindex="-1"' ); } } } }); -ariaTest('"aria-controls" attribute on role="tab"', exampleFile, 'tab-aria-controls', async (t) => { - t.plan(1); - await assertAriaControls(t, ex.tabSelector); -}); - -ariaTest('role="tabpanel" on div element', exampleFile, 'tabpanel-role', async (t) => { - t.plan(1); - await assertAriaRoles(t, 'ex1', 'tabpanel', ex.tabCount, 'div'); -}); - -ariaTest('"aria-label" attribute on role="tabpanel" elements', exampleFile, 'tabpanel-aria-label', async (t) => { - t.plan(1); - await assertAriaLabelExists(t, ex.tabSelector); -}); - -ariaTest('aria-roledescription="slide" on role="tabpanel" elements', exampleFile, 'tabpanel-roledescription', async (t) => { - t.plan(1); - await assertAttributeValues(t, ex.tabpanelSelector, 'aria-roledescription', 'slide'); -}); - -ariaTest('section has aria-roledescription set to carousel', exampleFile, 'carousel-region-aria-roledescription', async (t) => { - t.plan(1); - - // check the aria-roledescrption set to carousel - await assertAttributeValues(t, ex.landmarkSelector, 'aria-roledescription', 'carousel'); -}); - -ariaTest('section has aria-label', exampleFile, 'carousel-region-aria-label', async (t) => { - t.plan(1); - - await assertAriaLabelExists(t, ex.landmarkSelector); -}); - -ariaTest('slide container have aria-live initially set to off', exampleFile, 'carousel-aria-live', async (t) => { - t.plan(4); - - // On page load, `aria-level` is `off` - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'off'); - - // Focus on the widget, and aria-selected should change to 'polite' - await t.context.session.findElement(By.css(ex.tabSelectedSelector)).sendKeys(Key.ENTER); - - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'polite'); - - // Move focus to pause-start button start rotation, and the aria-live should change to 'polite' again - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).click(); - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'polite'); - - // Click the pause button, and the aria-selected should change to 'off' again - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).click(); - await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'off'); -}); - - -// Keys - -ariaTest('TAB key moves focus to open tab and panel', exampleFile, 'key-tab', async (t) => { - t.plan(ex.tabCount); - - for (let index = 0; index < ex.tabCount; index++) { - await openTabAtIndex(t, index); - - await assertTabOrder(t, ex.tabTabOrder[index]); +ariaTest( + '"aria-controls" attribute on role="tab"', + exampleFile, + 'tab-aria-controls', + async (t) => { + t.plan(1); + await assertAriaControls(t, ex.tabSelector); } -}); - -ariaTest('ARROW_RIGHT key moves focus and activates tab', exampleFile, 'key-right-arrow', async (t) => { - t.plan(3 * ex.tabCount); - - // Put focus on first tab - await openTabAtIndex(t, 0); - - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length - 1; index++) { - - // Send the arrow right key to move focus - await tabs[index].sendKeys(Key.ARROW_RIGHT); - - // Check the focus is correct - t.true( - await waitAndCheckFocus(t, ex.tabSelector, index + 1), - 'right arrow on tab "' + index + '" should put focus on the next tab.' - ); - - t.true( - await waitAndCheckAriaSelected(t, index + 1), - 'right arrow on tab "' + index + '" should set aria-selected="true" on next tab.' +); + +ariaTest( + 'role="tabpanel" on div element', + exampleFile, + 'tabpanel-role', + async (t) => { + t.plan(1); + await assertAriaRoles(t, 'ex1', 'tabpanel', ex.tabCount, 'div'); + } +); + +ariaTest( + '"aria-label" attribute on role="tabpanel" elements', + exampleFile, + 'tabpanel-aria-label', + async (t) => { + t.plan(1); + await assertAriaLabelExists(t, ex.tabSelector); + } +); + +ariaTest( + 'aria-roledescription="slide" on role="tabpanel" elements', + exampleFile, + 'tabpanel-roledescription', + async (t) => { + t.plan(1); + await assertAttributeValues( + t, + ex.tabpanelSelector, + 'aria-roledescription', + 'slide' ); - t.true( - await tabpanels[index + 1].isDisplayed(), - 'right arrow on tab "' + index + '" should display the next tab panel.' + } +); + +ariaTest( + 'section has aria-roledescription set to carousel', + exampleFile, + 'carousel-region-aria-roledescription', + async (t) => { + t.plan(1); + + // check the aria-roledescrption set to carousel + await assertAttributeValues( + t, + ex.landmarkSelector, + 'aria-roledescription', + 'carousel' ); } +); - // Send the arrow right key to move focus - await tabs[tabs.length - 1].sendKeys(Key.ARROW_RIGHT); +ariaTest( + 'section has aria-label', + exampleFile, + 'carousel-region-aria-label', + async (t) => { + t.plan(1); - // Check the focus returns to the first item - t.true( - await waitAndCheckFocus(t, ex.tabSelector, 0), - 'right arrow on tab "' + (tabs.length - 1) + '" should put focus to first tab.' - ); - t.true( - await waitAndCheckAriaSelected(t, 0), - 'right arrow on tab "' + (tabs.length - 1) + '" should set aria-selected="true" on first tab.' - ); - t.true( - await tabpanels[0].isDisplayed(), - 'right arrow on tab "' + (tabs.length - 1) + '" should display the first panel.' - ); - -}); - - -ariaTest('ARROW_LEFT key moves focus and activates tab', exampleFile, 'key-left-arrow', async (t) => { - t.plan(3 * ex.tabCount); + await assertAriaLabelExists(t, ex.landmarkSelector); + } +); + +ariaTest( + 'slide container have aria-live initially set to off', + exampleFile, + 'carousel-aria-live', + async (t) => { + t.plan(4); + + // On page load, `aria-level` is `off` + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'off' + ); - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + // Focus on the widget, and aria-selected should change to 'polite' + await t.context.session + .findElement(By.css(ex.tabSelectedSelector)) + .sendKeys(Key.ENTER); - // Put focus on first tab - await openTabAtIndex(t, 0); + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'polite' + ); - // Send the left arrow - await tabs[0].sendKeys(Key.ARROW_LEFT); + // Move focus to pause-start button start rotation, and the aria-live should change to 'polite' again + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .click(); + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'polite' + ); - // Check the focus returns to the last item - t.true( - await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), - 'left arrow on tab 0 should put focus to last tab.' - ); + // Click the pause button, and the aria-selected should change to 'off' again + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .click(); + await assertAttributeValues( + t, + ex.slideContainerSelector, + 'aria-live', + 'off' + ); + } +); - t.true( - await waitAndCheckAriaSelected(t, tabs.length - 1), - 'left arrow on tab 0 should set aria-selected="true" on last tab.' - ); - t.true( - await tabpanels[tabs.length - 1].isDisplayed(), - 'left arrow on tab 0 should display the last panel.' - ); +// Keys - for (let index = tabs.length - 1; index > 0; index--) { +ariaTest( + 'TAB key moves focus to open tab and panel', + exampleFile, + 'key-tab', + async (t) => { + t.plan(ex.tabCount); - // Send the arrow left key to move focus - await tabs[index].sendKeys(Key.ARROW_LEFT); + for (let index = 0; index < ex.tabCount; index++) { + await openTabAtIndex(t, index); - // Check the focus is correct - t.true( - await waitAndCheckFocus(t, ex.tabSelector, index - 1), - 'left arrow on tab "' + index + '" should put focus on the previous tab.' - ); - t.true( - await waitAndCheckAriaSelected(t, index - 1), - 'left arrow on tab "' + index + '" should set aria-selected="true" on previous tab.' - ); - t.true( - await tabpanels[index - 1].isDisplayed(), - 'left arrow on tab "' + index + '" should display the next previous panel.' - ); + await assertTabOrder(t, ex.tabTabOrder[index]); + } } -}); +); -ariaTest('HOME key moves focus and selects tab', exampleFile, 'key-home', async (t) => { - t.plan(3 * ex.tabCount); +ariaTest( + 'ARROW_RIGHT key moves focus and activates tab', + exampleFile, + 'key-right-arrow', + async (t) => { + t.plan(3 * ex.tabCount); - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length; index++) { + // Put focus on first tab + await openTabAtIndex(t, 0); - // Put focus on the tab - await openTabAtIndex(t, index); + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length - 1; index++) { + // Send the arrow right key to move focus + await tabs[index].sendKeys(Key.ARROW_RIGHT); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, index + 1), + 'right arrow on tab "' + index + '" should put focus on the next tab.' + ); - // Send the home key to the tab - await tabs[index].sendKeys(Key.HOME); + t.true( + await waitAndCheckAriaSelected(t, index + 1), + 'right arrow on tab "' + + index + + '" should set aria-selected="true" on next tab.' + ); + t.true( + await tabpanels[index + 1].isDisplayed(), + 'right arrow on tab "' + index + '" should display the next tab panel.' + ); + } - // Check the focus is correct + // Send the arrow right key to move focus + await tabs[tabs.length - 1].sendKeys(Key.ARROW_RIGHT); + + // Check the focus returns to the first item t.true( await waitAndCheckFocus(t, ex.tabSelector, 0), - 'home key on tab "' + index + '" should put focus on the first tab.' + 'right arrow on tab "' + + (tabs.length - 1) + + '" should put focus to first tab.' ); t.true( await waitAndCheckAriaSelected(t, 0), - 'home key on tab "' + index + '" should set aria-selected="true" on the first tab.' + 'right arrow on tab "' + + (tabs.length - 1) + + '" should set aria-selected="true" on first tab.' ); t.true( await tabpanels[0].isDisplayed(), - 'home key on tab "' + index + '" should display the first tab.' + 'right arrow on tab "' + + (tabs.length - 1) + + '" should display the first panel.' ); } -}); +); -ariaTest('END key moves focus and selects tab', exampleFile, 'key-end', async (t) => { - t.plan(3 * ex.tabCount); +ariaTest( + 'ARROW_LEFT key moves focus and activates tab', + exampleFile, + 'key-left-arrow', + async (t) => { + t.plan(3 * ex.tabCount); - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length; index++) { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - // Put focus on the tab - await openTabAtIndex(t, index); + // Put focus on first tab + await openTabAtIndex(t, 0); - // Send the end key to the tab - await tabs[index].sendKeys(Key.END); + // Send the left arrow + await tabs[0].sendKeys(Key.ARROW_LEFT); - // Check the focus is correct + // Check the focus returns to the last item t.true( await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), - 'home key on tab "' + index + '" should put focus on the last tab.' + 'left arrow on tab 0 should put focus to last tab.' ); + t.true( await waitAndCheckAriaSelected(t, tabs.length - 1), - 'home key on tab "' + index + '" should set aria-selected="true" on the last tab.' + 'left arrow on tab 0 should set aria-selected="true" on last tab.' ); t.true( await tabpanels[tabs.length - 1].isDisplayed(), - 'home key on tab "' + index + '" should display the last tab.' + 'left arrow on tab 0 should display the last panel.' ); - } -}); -// Keyboard interaction - -ariaTest('TAB moves key through buttons', exampleFile, 'rotation-key-tab', async (t) => { - t.plan(1); - - await assertTabOrder(t, ex.allFocusableItems); -}); - -ariaTest('ENTER pause and start carousel motion', exampleFile, 'rotation-enter-or-space-toggle', async (t) => { - t.plan(2); - - let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); + for (let index = tabs.length - 1; index > 0; index--) { + // Send the arrow left key to move focus + await tabs[index].sendKeys(Key.ARROW_LEFT); - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.ENTER); - // Move focus from widget - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, index - 1), + 'left arrow on tab "' + + index + + '" should put focus on the previous tab.' + ); + t.true( + await waitAndCheckAriaSelected(t, index - 1), + 'left arrow on tab "' + + index + + '" should set aria-selected="true" on previous tab.' + ); + t.true( + await tabpanels[index - 1].isDisplayed(), + 'left arrow on tab "' + + index + + '" should display the next previous panel.' + ); + } + } +); - let compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement === newActiveElement; - }, t.context.WaitTime); +ariaTest( + 'HOME key moves focus and selects tab', + exampleFile, + 'key-home', + async (t) => { + t.plan(3 * ex.tabCount); - t.true( - compareWithNextElement, - 'The active elements should stay the same when the pause button has been sent ENTER' - ); + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length; index++) { + // Put focus on the tab + await openTabAtIndex(t, index); + + // Send the home key to the tab + await tabs[index].sendKeys(Key.HOME); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, 0), + 'home key on tab "' + index + '" should put focus on the first tab.' + ); + t.true( + await waitAndCheckAriaSelected(t, 0), + 'home key on tab "' + + index + + '" should set aria-selected="true" on the first tab.' + ); + t.true( + await tabpanels[0].isDisplayed(), + 'home key on tab "' + index + '" should display the first tab.' + ); + } + } +); - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.ENTER); +ariaTest( + 'END key moves focus and selects tab', + exampleFile, + 'key-end', + async (t) => { + t.plan(3 * ex.tabCount); - compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement !== newActiveElement; - }, t.context.WaitTime); + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length; index++) { + // Put focus on the tab + await openTabAtIndex(t, index); + + // Send the end key to the tab + await tabs[index].sendKeys(Key.END); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), + 'home key on tab "' + index + '" should put focus on the last tab.' + ); + t.true( + await waitAndCheckAriaSelected(t, tabs.length - 1), + 'home key on tab "' + + index + + '" should set aria-selected="true" on the last tab.' + ); + t.true( + await tabpanels[tabs.length - 1].isDisplayed(), + 'home key on tab "' + index + '" should display the last tab.' + ); + } + } +); - t.true( - compareWithNextElement, - 'The active elements should change when the play button has been sent ENTER' - ); +// Keyboard interaction -}); +ariaTest( + 'TAB moves key through buttons', + exampleFile, + 'rotation-key-tab', + async (t) => { + t.plan(1); -ariaTest('SPACE pause and start carousel motion', exampleFile, 'rotation-enter-or-space-toggle', async (t) => { - t.plan(2); + await assertTabOrder(t, ex.allFocusableItems); + } +); + +ariaTest( + 'ENTER pause and start carousel motion', + exampleFile, + 'rotation-enter-or-space-toggle', + async (t) => { + t.plan(2); + + let activeElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.ENTER); + // Move focus from widget + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + let compareWithNextElement = await t.context.session.wait( + async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement === newActiveElement; + }, + t.context.WaitTime + ); - let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); + t.true( + compareWithNextElement, + 'The active elements should stay the same when the pause button has been sent ENTER' + ); - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.SPACE); - // Move focus from widget - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.ENTER); - let compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement === newActiveElement; - }, t.context.WaitTime); + compareWithNextElement = await t.context.session.wait(async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement !== newActiveElement; + }, t.context.WaitTime); - t.true( - compareWithNextElement, - 'The active elements should stay the same when the pause button has been sent SPACE' - ); + t.true( + compareWithNextElement, + 'The active elements should change when the play button has been sent ENTER' + ); + } +); + +ariaTest( + 'SPACE pause and start carousel motion', + exampleFile, + 'rotation-enter-or-space-toggle', + async (t) => { + t.plan(2); + + let activeElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.SPACE); + // Move focus from widget + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + let compareWithNextElement = await t.context.session.wait( + async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement === newActiveElement; + }, + t.context.WaitTime + ); - await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.SPACE); + t.true( + compareWithNextElement, + 'The active elements should stay the same when the pause button has been sent SPACE' + ); - compareWithNextElement = await t.context.session.wait(async function () { - let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label'); - return activeElement !== newActiveElement; - }, t.context.WaitTime); + await t.context.session + .findElement(By.css(ex.pausePlayButtonSelector)) + .sendKeys(Key.SPACE); - t.true( - compareWithNextElement, - 'The active elements should change when the play button has been sent SPACE' - ); + compareWithNextElement = await t.context.session.wait(async function () { + let newActiveElement = await t.context.session + .findElement(By.css(ex.activeCarouselItem)) + .getAttribute('aria-label'); + return activeElement !== newActiveElement; + }, t.context.WaitTime); -}); + t.true( + compareWithNextElement, + 'The active elements should change when the play button has been sent SPACE' + ); + } +); diff --git a/test/tests/checkbox_checkbox-1.js b/test/tests/checkbox_checkbox-1.js index b69e1673d5..dd60628132 100644 --- a/test/tests/checkbox_checkbox-1.js +++ b/test/tests/checkbox_checkbox-1.js @@ -16,32 +16,36 @@ const ex = { '#ex1 li:nth-of-type(1) [role="checkbox"]', '#ex1 li:nth-of-type(2) [role="checkbox"]', '#ex1 li:nth-of-type(3) [role="checkbox"]', - '#ex1 li:nth-of-type(4) [role="checkbox"]' + '#ex1 li:nth-of-type(4) [role="checkbox"]', ], - defaultSelectedCheckboxes: [ - '#ex1 li:nth-of-type(2) [role="checkbox"]' - ] + defaultSelectedCheckboxes: ['#ex1 li:nth-of-type(2) [role="checkbox"]'], }; const waitAndCheckAriaChecked = async function (t, selector, value) { - return t.context.session.wait( - async function () { - let checkbox = await t.context.session.findElement(By.css(selector)); - return (await checkbox.getAttribute('aria-checked')) === value; - }, - t.context.waitTime, - 'Timeout: aria-checked is not set to "' + value + '" for: ' + selector, - ).catch((err) => { return err; }); + return t.context.session + .wait( + async function () { + let checkbox = await t.context.session.findElement(By.css(selector)); + return (await checkbox.getAttribute('aria-checked')) === value; + }, + t.context.waitTime, + 'Timeout: aria-checked is not set to "' + value + '" for: ' + selector + ) + .catch((err) => { + return err; + }); }; const checkVisuallyChecked = async function (t, selector, checked) { - let expectedBackgroundColor = checked ? 'rgb(96, 155, 251)' : 'rgba(0, 0, 0, 0)'; + let expectedBackgroundColor = checked + ? 'rgb(96, 155, 251)' + : 'rgba(0, 0, 0, 0)'; let background = await t.context.session.executeScript(function () { - const [ selector ] = arguments; - return window.getComputedStyle( - document.querySelector(selector), ':before' - ).getPropertyValue('background-color'); + const [selector] = arguments; + return window + .getComputedStyle(document.querySelector(selector), ':before') + .getPropertyValue('background-color'); }, selector); return background === expectedBackgroundColor; @@ -56,7 +60,6 @@ const uncheckAllSelectedByDefault = async function (t) { // Attributes ariaTest('element h3 exists', exampleFile, 'h3', async (t) => { - let header = await t.context.queryElements(t, '#ex1 h3'); t.is( @@ -69,106 +72,150 @@ ariaTest('element h3 exists', exampleFile, 'h3', async (t) => { await header[0].getText(), 'One h3 element exist with readable content within the example to label the checkboxes' ); - }); -ariaTest('role="group" element exists', exampleFile, 'group-role', async (t) => { +ariaTest( + 'role="group" element exists', + exampleFile, + 'group-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'group', '1', 'div'); -}); + } +); -ariaTest('"aria-labelledby" on group element', exampleFile, 'group-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on group element', + exampleFile, + 'group-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.groupSelector); -}); + } +); -ariaTest('role="checkbox" elements exist', exampleFile, 'checkbox-role', async (t) => { +ariaTest( + 'role="checkbox" elements exist', + exampleFile, + 'checkbox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'checkbox', '4', 'div'); - // Test that each checkbox has an accessible name - // In this case, the accessible name is the text within the div - let checkboxes = await t.context.queryElements(t, ex.checkboxSelector); - - for (let index = 0; index < checkboxes.length; index++) { - let text = await checkboxes[index].getText(); - t.true( - typeof text === 'string' && text.length > 0, - 'checkbox div at index: ' + index + ' should have contain text describing the textbox' - ); + // Test that each checkbox has an accessible name + // In this case, the accessible name is the text within the div + let checkboxes = await t.context.queryElements(t, ex.checkboxSelector); + + for (let index = 0; index < checkboxes.length; index++) { + let text = await checkboxes[index].getText(); + t.true( + typeof text === 'string' && text.length > 0, + 'checkbox div at index: ' + + index + + ' should have contain text describing the textbox' + ); + } } -}); +); -ariaTest('tabindex="0" for checkbox elements', exampleFile, 'checkbox-tabindex', async (t) => { +ariaTest( + 'tabindex="0" for checkbox elements', + exampleFile, + 'checkbox-tabindex', + async (t) => { await assertAttributeValues(t, ex.checkboxSelector, 'tabindex', '0'); -}); - -ariaTest('"aria-checked" on checkbox element', exampleFile, 'checkbox-aria-checked', async (t) => { - - await uncheckAllSelectedByDefault(t); - - // check the aria-checked attribute is false to begin - await assertAttributeValues(t, ex.checkboxSelector, 'aria-checked', 'false'); - - // check that the visual indicator matches the checked state (unchecked) - for (let checkboxSelector of ex.checkboxes) { - t.true( - await checkVisuallyChecked(t, checkboxSelector, false), - 'All checkboxes should be visually checked' - ); } +); + +ariaTest( + '"aria-checked" on checkbox element', + exampleFile, + 'checkbox-aria-checked', + async (t) => { + await uncheckAllSelectedByDefault(t); + + // check the aria-checked attribute is false to begin + await assertAttributeValues( + t, + ex.checkboxSelector, + 'aria-checked', + 'false' + ); - // Click all checkboxes to select them - let checkboxes = await t.context.queryElements(t, ex.checkboxSelector); - for (let checkbox of checkboxes) { - await checkbox.click(); + // check that the visual indicator matches the checked state (unchecked) + for (let checkboxSelector of ex.checkboxes) { + t.true( + await checkVisuallyChecked(t, checkboxSelector, false), + 'All checkboxes should be visually checked' + ); + } + + // Click all checkboxes to select them + let checkboxes = await t.context.queryElements(t, ex.checkboxSelector); + for (let checkbox of checkboxes) { + await checkbox.click(); + } + + // check the aria-checked attribute has been updated to true + await assertAttributeValues(t, ex.checkboxSelector, 'aria-checked', 'true'); + + // check that the visual indicator matches the checked state (checked) + for (let checkboxSelector of ex.checkboxes) { + t.true( + await checkVisuallyChecked(t, checkboxSelector, true), + 'All checkboxes should be visually checked' + ); + } } - - // check the aria-checked attribute has been updated to true - await assertAttributeValues(t, ex.checkboxSelector, 'aria-checked', 'true'); - - // check that the visual indicator matches the checked state (checked) - for (let checkboxSelector of ex.checkboxes) { - t.true( - await checkVisuallyChecked(t, checkboxSelector, true), - 'All checkboxes should be visually checked' - ); +); + +ariaTest( + 'key TAB moves focus between checkboxes', + exampleFile, + 'key-tab', + async (t) => { + await assertTabOrder(t, ex.checkboxes); } -}); - -ariaTest('key TAB moves focus between checkboxes', exampleFile, 'key-tab', async (t) => { - - await assertTabOrder(t, ex.checkboxes); -}); - -ariaTest('key SPACE selects or unselects checkbox', exampleFile, 'key-space', async (t) => { - - await uncheckAllSelectedByDefault(t); - - for (let checkboxSelector of ex.checkboxes) { - - // Send SPACE key to check box to select - await t.context.session.findElement(By.css(checkboxSelector)).sendKeys(Key.SPACE); - - t.true( - await waitAndCheckAriaChecked(t, checkboxSelector, 'true'), - 'aria-selected should be set after sending SPACE key to checkbox: ' + checkboxSelector - ); - - t.true( - await checkVisuallyChecked(t, checkboxSelector, true), - 'checkbox should be visual checked after sending SPACE key to checkbox: ' + checkboxSelector - ); - - // Send SPACE key to check box to unselect - await t.context.session.findElement(By.css(checkboxSelector)).sendKeys(Key.SPACE); - - t.true( - await waitAndCheckAriaChecked(t, checkboxSelector, 'false'), - 'aria-selected should be set after sending SPACE key to checkbox: ' + checkboxSelector - ); - - t.true( - await checkVisuallyChecked(t, checkboxSelector, false), - 'checkbox should be visual checked after sending SPACE key to checkbox: ' + checkboxSelector - ); +); + +ariaTest( + 'key SPACE selects or unselects checkbox', + exampleFile, + 'key-space', + async (t) => { + await uncheckAllSelectedByDefault(t); + + for (let checkboxSelector of ex.checkboxes) { + // Send SPACE key to check box to select + await t.context.session + .findElement(By.css(checkboxSelector)) + .sendKeys(Key.SPACE); + + t.true( + await waitAndCheckAriaChecked(t, checkboxSelector, 'true'), + 'aria-selected should be set after sending SPACE key to checkbox: ' + + checkboxSelector + ); + + t.true( + await checkVisuallyChecked(t, checkboxSelector, true), + 'checkbox should be visual checked after sending SPACE key to checkbox: ' + + checkboxSelector + ); + + // Send SPACE key to check box to unselect + await t.context.session + .findElement(By.css(checkboxSelector)) + .sendKeys(Key.SPACE); + + t.true( + await waitAndCheckAriaChecked(t, checkboxSelector, 'false'), + 'aria-selected should be set after sending SPACE key to checkbox: ' + + checkboxSelector + ); + + t.true( + await checkVisuallyChecked(t, checkboxSelector, false), + 'checkbox should be visual checked after sending SPACE key to checkbox: ' + + checkboxSelector + ); + } } - -}); +); diff --git a/test/tests/checkbox_checkbox-2.js b/test/tests/checkbox_checkbox-2.js index ec717e4243..355bfe6236 100644 --- a/test/tests/checkbox_checkbox-2.js +++ b/test/tests/checkbox_checkbox-2.js @@ -18,9 +18,9 @@ const ex = { '#ex1 #cond1', '#ex1 #cond2', '#ex1 #cond3', - '#ex1 #cond4' + '#ex1 #cond4', ], - numCondiments: 4 + numCondiments: 4, }; const uncheckAllConds = async function (t) { @@ -32,182 +32,235 @@ const uncheckAllConds = async function (t) { // Attributes -ariaTest('role="checkbox" element exists', exampleFile, 'checkbox-role', async (t) => { +ariaTest( + 'role="checkbox" element exists', + exampleFile, + 'checkbox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'checkbox', '1', 'div'); -}); + } +); -ariaTest('"tabindex" on checkbox element', exampleFile, 'checkbox-tabindex', async (t) => { +ariaTest( + '"tabindex" on checkbox element', + exampleFile, + 'checkbox-tabindex', + async (t) => { await assertAttributeValues(t, ex.checkboxSelector, 'tabindex', '0'); -}); + } +); + +ariaTest( + '"aria-controls" ', + exampleFile, + 'checkbox-aria-controls', + async (t) => { + const checkbox = await t.context.session.findElement( + By.css(ex.checkboxSelector) + ); + const controls = (await checkbox.getAttribute('aria-controls')).split(' '); + + t.is( + controls.length, + ex.numCondiments, + 'The attribute "aria-controls" should have ' + + ex.numCondiments + + ' values seperated by spaces' + ); + + for (let id of controls) { + t.is( + (await t.context.queryElements(t, `#${id}`)).length, + 1, + 'An element with id ' + id + ' should exist' + ); + } + } +); -ariaTest('"aria-controls" ', exampleFile, 'checkbox-aria-controls', async (t) => { +ariaTest( + '"aria-checked" on checkbox element', + exampleFile, + 'checkbox-aria-checked-false', + async (t) => { + await uncheckAllConds(t); + let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); - const checkbox = await t.context.session.findElement(By.css(ex.checkboxSelector)); - const controls = (await checkbox.getAttribute('aria-controls')).split(' '); + t.is( + await checkbox.getAttribute('aria-checked'), + 'false', + 'The control checkbox should have attribute aria-checked = "false" when not condiments are checked via manually unchecking all boxes' + ); - t.is( - controls.length, - ex.numCondiments, - 'The attribute "aria-controls" should have ' + ex.numCondiments + ' values seperated by spaces' - ); + // Click the checkbox twice to cycle it back to checked='false' + await checkbox.click(); + await checkbox.click(); - for (let id of controls) { + // Make sure all condiments are still not selected t.is( - (await t.context.queryElements(t, `#${id}`)).length, + await checkbox.getAttribute('aria-checked'), + 'false', + 'The control checkbox should have attribute aria-checked = "false" after clicking checkbox twice (with no parially checked state)' + ); + + assertNoElements( + t, + ex.checkedCondsSelector, + 'No condiments should be selected via: ' + ex.checkedCondsSelector + ); + } +); + +ariaTest( + '"aria-checked" on checkbox element', + exampleFile, + 'checkbox-aria-checked-mixed', + async (t) => { + await uncheckAllConds(t); + let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); + + // Check one box + const condiments = await t.context.queryElements(t, ex.condimentsSelector); + await condiments[0].click(); + + t.is( + await checkbox.getAttribute('aria-checked'), + 'mixed', + 'The control checkbox should have attribute aria-checked = "mixed" when only some condiments are checked' + ); + + // Click the checkbox three times to cycle it back to checked='true' + await checkbox.click(); + await checkbox.click(); + await checkbox.click(); + + // Make sure all condiments are still not selected + t.is( + await checkbox.getAttribute('aria-checked'), + 'mixed', + 'The control checkbox should have attribute aria-checked = "mixed" after clicking checkbox three times' + ); + + t.is( + (await t.context.queryElements(t, ex.checkedCondsSelector)).length, 1, - 'An element with id ' + id + ' should exist' + '1 condiments should be selected via: ' + ex.checkedCondsSelector + ); + } +); + +ariaTest( + '"aria-checked" on checkbox element', + exampleFile, + 'checkbox-aria-checked-true', + async (t) => { + await uncheckAllConds(t); + let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); + + // Check the all boxes + const condiments = await t.context.queryElements(t, ex.condimentsSelector); + for (let cond of condiments) { + await cond.click(); + } + + t.is( + await checkbox.getAttribute('aria-checked'), + 'true', + 'The control checkbox should have attribute aria-checked = "true" when only some condiments are checked' + ); + + // Click the checkbox twice to cycle it back to checked='true' + await checkbox.click(); + await checkbox.click(); + + // Make sure all condiments are still selected + t.is( + await checkbox.getAttribute('aria-checked'), + 'true', + 'The control checkbox should have attribute aria-checked = "true" after clicking checkbox twice (with no parially checked state)' + ); + + t.is( + (await t.context.queryElements(t, ex.checkedCondsSelector)).length, + ex.numCondiments, + ex.numCondiments + + ' condiments should be selected via: ' + + ex.checkedCondsSelector ); } -}); - -ariaTest('"aria-checked" on checkbox element', exampleFile, 'checkbox-aria-checked-false', async (t) => { - - await uncheckAllConds(t); - let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); - - t.is( - await checkbox.getAttribute('aria-checked'), - 'false', - 'The control checkbox should have attribute aria-checked = "false" when not condiments are checked via manually unchecking all boxes' - ); - - // Click the checkbox twice to cycle it back to checked='false' - await checkbox.click(); - await checkbox.click(); - - // Make sure all condiments are still not selected - t.is( - await checkbox.getAttribute('aria-checked'), - 'false', - 'The control checkbox should have attribute aria-checked = "false" after clicking checkbox twice (with no parially checked state)' - ); - - assertNoElements(t, ex.checkedCondsSelector, 'No condiments should be selected via: ' + ex.checkedCondsSelector); -}); - -ariaTest('"aria-checked" on checkbox element', exampleFile, 'checkbox-aria-checked-mixed', async (t) => { - - await uncheckAllConds(t); - let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); - - // Check one box - const condiments = await t.context.queryElements(t, ex.condimentsSelector); - await condiments[0].click(); - - t.is( - await checkbox.getAttribute('aria-checked'), - 'mixed', - 'The control checkbox should have attribute aria-checked = "mixed" when only some condiments are checked' - ); - - // Click the checkbox three times to cycle it back to checked='true' - await checkbox.click(); - await checkbox.click(); - await checkbox.click(); - - // Make sure all condiments are still not selected - t.is( - await checkbox.getAttribute('aria-checked'), - 'mixed', - 'The control checkbox should have attribute aria-checked = "mixed" after clicking checkbox three times' - ); - - t.is( - (await t.context.queryElements(t, ex.checkedCondsSelector)).length, - 1, - '1 condiments should be selected via: ' + ex.checkedCondsSelector - ); -}); - -ariaTest('"aria-checked" on checkbox element', exampleFile, 'checkbox-aria-checked-true', async (t) => { - - await uncheckAllConds(t); - let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); - - // Check the all boxes - const condiments = await t.context.queryElements(t, ex.condimentsSelector); - for (let cond of condiments) { - await cond.click(); +); + +ariaTest( + 'key TAB moves focus between checkboxes', + exampleFile, + 'key-tab', + async (t) => { + await assertTabOrder(t, ex.allCheckboxes); } +); + +ariaTest( + 'key SPACE selects or unselects checkbox', + exampleFile, + 'key-space', + async (t) => { + // Check one box + await uncheckAllConds(t); + const condiments = await t.context.queryElements(t, ex.condimentsSelector); + await condiments[0].click(); + + // Send SPACE key to checkbox to change state + let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); + await checkbox.sendKeys(Key.SPACE); + + // Check that the state + t.is( + await checkbox.getAttribute('aria-checked'), + 'true', + 'After sending SPACE to the checkbox in a mixed state, aria-checked should equal "true"' + ); - t.is( - await checkbox.getAttribute('aria-checked'), - 'true', - 'The control checkbox should have attribute aria-checked = "true" when only some condiments are checked' - ); - - // Click the checkbox twice to cycle it back to checked='true' - await checkbox.click(); - await checkbox.click(); - - // Make sure all condiments are still selected - t.is( - await checkbox.getAttribute('aria-checked'), - 'true', - 'The control checkbox should have attribute aria-checked = "true" after clicking checkbox twice (with no parially checked state)' - ); - - t.is( - (await t.context.queryElements(t, ex.checkedCondsSelector)).length, - ex.numCondiments, - ex.numCondiments + ' condiments should be selected via: ' + ex.checkedCondsSelector - ); -}); - -ariaTest('key TAB moves focus between checkboxes', exampleFile, 'key-tab', async (t) => { - - await assertTabOrder(t, ex.allCheckboxes); -}); - -ariaTest('key SPACE selects or unselects checkbox', exampleFile, 'key-space', async (t) => { - - // Check one box - await uncheckAllConds(t); - const condiments = await t.context.queryElements(t, ex.condimentsSelector); - await condiments[0].click(); - - // Send SPACE key to checkbox to change state - let checkbox = t.context.session.findElement(By.css(ex.checkboxSelector)); - await checkbox.sendKeys(Key.SPACE); - - // Check that the state - t.is( - await checkbox.getAttribute('aria-checked'), - 'true', - 'After sending SPACE to the checkbox in a mixed state, aria-checked should equal "true"' - ); - - t.is( - (await t.context.queryElements(t, ex.checkedCondsSelector)).length, - ex.numCondiments, - 'After sending SPACE to the checkbox in a mixed state, ' + ex.numCondiments + ' condiments should be selected via: ' + ex.checkedCondsSelector - ); - - // Send SPACE key to checkbox to change state - await checkbox.sendKeys(Key.SPACE); - - // Check that the state - t.is( - await checkbox.getAttribute('aria-checked'), - 'false', - 'After sending SPACE to the checkbox in a all-checked state, aria-checked should equal "false"' - ); - - assertNoElements(t, ex.checkedCondsSelector, 'After sending SPACE to the checkbox in a check state, 0 condiments should be selected via: ' + ex.checkedCondsSelector); - - // Send SPACE key to checkbox to change state - await checkbox.sendKeys(Key.SPACE); - - // Check that the state - t.is( - await checkbox.getAttribute('aria-checked'), - 'mixed', - 'After sending SPACE to the checkbox in an all-unchecked state, aria-checked should equal "mixed"' - ); - - t.is( - (await t.context.queryElements(t, ex.checkedCondsSelector)).length, - 1, - 'After sending SPACE to the checkbox in a uncheck state, 1 condiments should be selected via: ' + ex.checkedCondsSelector - ); -}); + t.is( + (await t.context.queryElements(t, ex.checkedCondsSelector)).length, + ex.numCondiments, + 'After sending SPACE to the checkbox in a mixed state, ' + + ex.numCondiments + + ' condiments should be selected via: ' + + ex.checkedCondsSelector + ); + + // Send SPACE key to checkbox to change state + await checkbox.sendKeys(Key.SPACE); + + // Check that the state + t.is( + await checkbox.getAttribute('aria-checked'), + 'false', + 'After sending SPACE to the checkbox in a all-checked state, aria-checked should equal "false"' + ); + + assertNoElements( + t, + ex.checkedCondsSelector, + 'After sending SPACE to the checkbox in a check state, 0 condiments should be selected via: ' + + ex.checkedCondsSelector + ); + + // Send SPACE key to checkbox to change state + await checkbox.sendKeys(Key.SPACE); + + // Check that the state + t.is( + await checkbox.getAttribute('aria-checked'), + 'mixed', + 'After sending SPACE to the checkbox in an all-unchecked state, aria-checked should equal "mixed"' + ); + + t.is( + (await t.context.queryElements(t, ex.checkedCondsSelector)).length, + 1, + 'After sending SPACE to the checkbox in a uncheck state, 1 condiments should be selected via: ' + + ex.checkedCondsSelector + ); + } +); diff --git a/test/tests/combobox_autocomplete-both.js b/test/tests/combobox_autocomplete-both.js index d82640b9d1..c4606fe953 100644 --- a/test/tests/combobox_autocomplete-both.js +++ b/test/tests/combobox_autocomplete-both.js @@ -4,7 +4,7 @@ const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); const assertAriaLabelExists = require('../util/assertAriaLabelExists'); -const assertAttributeDNE = require('../util/assertAttributeDNE'); +const assertAttributeDNE = require('../util/assertAttributeDNE'); const assertAriaRoles = require('../util/assertAriaRoles'); const assertAriaSelectedAndActivedescendant = require('../util/assertAriaSelectedAndActivedescendant'); @@ -16,214 +16,310 @@ const ex = { optionsSelector: '#ex1 [role="option"]', buttonSelector: '#ex1 button', numAOptions: 5, - secondAOption: 'Alaska' - + secondAOption: 'Alaska', }; const waitForFocusChange = async (t, textboxSelector, originalFocus) => { - await t.context.session.wait(async function () { - let newfocus = await t.context.session - .findElement(By.css(textboxSelector)) - .getAttribute('aria-activedescendant'); - return newfocus != originalFocus; - }, t.context.waitTime, 'Timeout waiting for "aria-activedescendant" value to change from: ' + originalFocus); + await t.context.session.wait( + async function () { + let newfocus = await t.context.session + .findElement(By.css(textboxSelector)) + .getAttribute('aria-activedescendant'); + return newfocus != originalFocus; + }, + t.context.waitTime, + 'Timeout waiting for "aria-activedescendant" value to change from: ' + + originalFocus + ); }; const confirmCursorIndex = async (t, selector, cursorIndex) => { - return t.context.session.executeScript(function () { - const [selector, cursorIndex] = arguments; - let item = document.querySelector(selector); - return item.selectionStart === cursorIndex; - }, selector, cursorIndex); + return t.context.session.executeScript( + function () { + const [selector, cursorIndex] = arguments; + let item = document.querySelector(selector); + return item.selectionStart === cursorIndex; + }, + selector, + cursorIndex + ); }; // Attributes -ariaTest('Test for role="combobox"', exampleFile, 'combobox-role', async (t) => { +ariaTest( + 'Test for role="combobox"', + exampleFile, + 'combobox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'combobox', '1', 'input'); -}); - -ariaTest('"aria-autocomplete" on comboxbox element', exampleFile, 'combobox-aria-autocomplete', async (t) => { - await assertAttributeValues(t, ex.textboxSelector, 'aria-autocomplete', 'both'); -}); - -ariaTest('"aria-controls" attribute on combobox element', exampleFile, 'combobox-aria-controls', async (t) => { - - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); - - t.truthy( - popupId, - '"aria-controls" attribute should exist on: ' + ex.textboxSelector - ); - - const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - - t.is( - popupElements.length, - 1, - 'There should be a element with id "' + popupId + '" as referenced by the aria-controls attribute' - ); -}); - -ariaTest('"aria-expanded" on combobox element', exampleFile, 'combobox-aria-expanded', async (t) => { - - const combobox = await t.context.session.findElement(By.css(ex.textboxSelector)); + } +); + +ariaTest( + '"aria-autocomplete" on comboxbox element', + exampleFile, + 'combobox-aria-autocomplete', + async (t) => { + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-autocomplete', + 'both' + ); + } +); + +ariaTest( + '"aria-controls" attribute on combobox element', + exampleFile, + 'combobox-aria-controls', + async (t) => { + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - // Check that aria-expanded is false and the listbox is not visible before interacting + t.truthy( + popupId, + '"aria-controls" attribute should exist on: ' + ex.textboxSelector + ); - t.is( - await combobox.getAttribute('aria-expanded'), - 'false', - 'combobox element should have attribute "aria-expanded" set to false by default.' - ); + const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); + t.is( + popupElements.length, + 1, + 'There should be a element with id "' + + popupId + + '" as referenced by the aria-controls attribute' + ); + } +); + +ariaTest( + '"aria-expanded" on combobox element', + exampleFile, + 'combobox-aria-expanded', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); - const popupElement = await t.context.session - .findElement(By.id('ex1')) - .findElement(By.id(popupId)); + // Check that aria-expanded is false and the listbox is not visible before interacting - t.false( - await popupElement.isDisplayed(), - 'Popup element should not be displayed when \'aria-expanded\' is false\'' - ); + t.is( + await combobox.getAttribute('aria-expanded'), + 'false', + 'combobox element should have attribute "aria-expanded" set to false by default.' + ); - // Send key "a" to textbox + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - await t.context.session - .findElement(By.css(ex.textboxSelector)) - .sendKeys('a'); + const popupElement = await t.context.session + .findElement(By.id('ex1')) + .findElement(By.id(popupId)); - // Check that aria-expanded is true and the listbox is visible + t.false( + await popupElement.isDisplayed(), + "Popup element should not be displayed when 'aria-expanded' is false'" + ); - t.is( - await combobox.getAttribute('aria-expanded'), - 'true', - 'combobox element should have attribute "aria-expand" set to true after typing.' - ); + // Send key "a" to textbox - t.true( - await popupElement.isDisplayed(), - 'Popup element should be displayed when \'aria-expanded\' is true\'' - ); + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); -}); + // Check that aria-expanded is true and the listbox is visible -ariaTest('"aria-activedescendant" on combobox element', exampleFile, 'combobox-aria-activedescendant', async (t) => { - await assertAttributeValues(t, ex.textboxSelector, 'aria-activedescendant', null); -}); + t.is( + await combobox.getAttribute('aria-expanded'), + 'true', + 'combobox element should have attribute "aria-expand" set to true after typing.' + ); -ariaTest('role "listbox" on ul element', exampleFile, 'listbox-role', async (t) => { + t.true( + await popupElement.isDisplayed(), + "Popup element should be displayed when 'aria-expanded' is true'" + ); + } +); + +ariaTest( + '"aria-activedescendant" on combobox element', + exampleFile, + 'combobox-aria-activedescendant', + async (t) => { + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-activedescendant', + null + ); + } +); + +ariaTest( + 'role "listbox" on ul element', + exampleFile, + 'listbox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'listbox', '1', 'ul'); -}); - -ariaTest('"aria-label" attribute on listbox element', exampleFile, 'listbox-aria-label', async (t) => { + } +); + +ariaTest( + '"aria-label" attribute on listbox element', + exampleFile, + 'listbox-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.listboxSelector); -}); - -ariaTest('role "option" on lu elements', exampleFile, 'option-role', async (t) => { - - // Send arrow down to reveal all options - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys(Key.ARROW_DOWN); - await assertAriaRoles(t, 'ex1', 'option', '56', 'li'); -}); - -ariaTest('"aria-selected" attribute on options element', exampleFile, 'option-aria-selected', async (t) => { - - // Send key "a" - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys('a'); - await assertAttributeValues(t, ex.optionsSelector + ':nth-of-type(1)', 'aria-selected', 'true'); -}); - -ariaTest('Button should have tabindex="-1"', exampleFile, 'button-tabindex', async (t) => { - const button = await t.context.session.findElement(By.css(ex.buttonSelector)) - - t.is( - await button.getAttribute('tabindex'), - '-1', - 'tabindex should be set to "-1" on button' - ); -}); - -ariaTest('"aria-label" attribute on button element', exampleFile, 'button-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.buttonSelector); -}); - - -ariaTest('"aria-controls" attribute on button element', exampleFile, 'button-aria-controls', async (t) => { - const popupId = await t.context.session - .findElement(By.css(ex.buttonSelector)) - .getAttribute('aria-controls'); - - t.truthy( - popupId, - '"aria-controls" attribute should exist on: ' + ex.buttonSelector - ); - - const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); + } +); + +ariaTest( + 'role "option" on lu elements', + exampleFile, + 'option-role', + async (t) => { + // Send arrow down to reveal all options + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys(Key.ARROW_DOWN); + await assertAriaRoles(t, 'ex1', 'option', '56', 'li'); + } +); + +ariaTest( + '"aria-selected" attribute on options element', + exampleFile, + 'option-aria-selected', + async (t) => { + // Send key "a" + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); + await assertAttributeValues( + t, + ex.optionsSelector + ':nth-of-type(1)', + 'aria-selected', + 'true' + ); + } +); + +ariaTest( + 'Button should have tabindex="-1"', + exampleFile, + 'button-tabindex', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); - t.is( - popupElements.length, - 1, - 'There should be a element with id "' + popupId + '" as referenced by the aria-controls attribute' - ); -}); + t.is( + await button.getAttribute('tabindex'), + '-1', + 'tabindex should be set to "-1" on button' + ); + } +); + +ariaTest( + '"aria-label" attribute on button element', + exampleFile, + 'button-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.buttonSelector); + } +); + +ariaTest( + '"aria-controls" attribute on button element', + exampleFile, + 'button-aria-controls', + async (t) => { + const popupId = await t.context.session + .findElement(By.css(ex.buttonSelector)) + .getAttribute('aria-controls'); + + t.truthy( + popupId, + '"aria-controls" attribute should exist on: ' + ex.buttonSelector + ); -ariaTest('"aria-expanded" on button element', exampleFile, 'button-aria-expanded', async (t) => { - const button = await t.context.session.findElement(By.css(ex.buttonSelector)); + const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - // Check that aria-expanded is false and the listbox is not visible before interacting + t.is( + popupElements.length, + 1, + 'There should be a element with id "' + + popupId + + '" as referenced by the aria-controls attribute' + ); + } +); + +ariaTest( + '"aria-expanded" on button element', + exampleFile, + 'button-aria-expanded', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); - t.is( - await button.getAttribute('aria-expanded'), - 'false', - 'button element should have attribute "aria-expanded" set to false by default.' - ); + // Check that aria-expanded is false and the listbox is not visible before interacting - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); + t.is( + await button.getAttribute('aria-expanded'), + 'false', + 'button element should have attribute "aria-expanded" set to false by default.' + ); - const popupElement = await t.context.session - .findElement(By.id('ex1')) - .findElement(By.id(popupId)); + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - t.false( - await popupElement.isDisplayed(), - 'Popup element should not be displayed when \'aria-expanded\' is \'false\'' - ); + const popupElement = await t.context.session + .findElement(By.id('ex1')) + .findElement(By.id(popupId)); - // Send key "a" to textbox + t.false( + await popupElement.isDisplayed(), + "Popup element should not be displayed when 'aria-expanded' is 'false'" + ); - await t.context.session - .findElement(By.css(ex.textboxSelector)) - .sendKeys('a'); + // Send key "a" to textbox - // Check that aria-expanded is true and the listbox is visible + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); - t.is( - await button.getAttribute('aria-expanded'), - 'true', - 'button element should have attribute "aria-expand" set to true after typing.' - ); + // Check that aria-expanded is true and the listbox is visible - t.true( - await popupElement.isDisplayed(), - 'Popup element should be displayed when \'aria-expanded\' is \'true\'' - ); + t.is( + await button.getAttribute('aria-expanded'), + 'true', + 'button element should have attribute "aria-expand" set to true after typing.' + ); -}); + t.true( + await popupElement.isDisplayed(), + "Popup element should be displayed when 'aria-expanded' is 'true'" + ); + } +); // Keys -ariaTest('Test alt + down key press with focus on textbox', - exampleFile, 'textbox-key-alt-down-arrow', async (t) => { - - +ariaTest( + 'Test alt + down key press with focus on textbox', + exampleFile, + 'textbox-key-alt-down-arrow', + async (t) => { // Send ARROW_DOWN to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -231,19 +327,21 @@ ariaTest('Test alt + down key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example the list box should display after ALT + ARROW_DOWN keypress' ); await assertAttributeDNE(t, ex.optionsSelector, 'aria-selected'); - - }); - - -ariaTest('Test down key press with focus on textbox', - exampleFile, 'textbox-key-down-arrow', async (t) => { - - + } +); + +ariaTest( + 'Test down key press with focus on textbox', + exampleFile, + 'textbox-key-down-arrow', + async (t) => { // Send ARROW_DOWN to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -251,7 +349,9 @@ ariaTest('Test down key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example ex3 listbox should display after ARROW_DOWN keypress' ); @@ -259,15 +359,20 @@ ariaTest('Test down key press with focus on textbox', await waitForFocusChange(t, ex.textboxSelector, null); // Check that the active descendent focus is correct - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, 0); - - }); - - -ariaTest('Test down key press with focus on list', - exampleFile, 'listbox-key-down-arrow', async (t) => { - - + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + 0 + ); + } +); + +ariaTest( + 'Test down key press with focus on list', + exampleFile, + 'listbox-key-down-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_DOWN to textbox to set focus on listbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -286,16 +391,21 @@ ariaTest('Test down key press with focus on list', // Account for race condition await waitForFocusChange(t, ex.textboxSelector, oldfocus); - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, i % ex.numAOptions); + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + i % ex.numAOptions + ); } - - }); - - -ariaTest('Test up key press with focus on textbox', - exampleFile, 'textbox-key-up-arrow', async (t) => { - - + } +); + +ariaTest( + 'Test up key press with focus on textbox', + exampleFile, + 'textbox-key-up-arrow', + async (t) => { // Send ARROW_UP to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -303,7 +413,9 @@ ariaTest('Test up key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example ex3 listbox should display after ARROW_UP keypress' ); @@ -311,15 +423,22 @@ ariaTest('Test up key press with focus on textbox', await waitForFocusChange(t, ex.textboxSelector, null); // Check that the active descendent focus is correct - let numOptions = (await t.context.queryElements(t, ex.optionsSelector)).length; - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, numOptions - 1); - }); - - -ariaTest('Test up key press with focus on listbox', - exampleFile, 'listbox-key-up-arrow', async (t) => { - - + let numOptions = (await t.context.queryElements(t, ex.optionsSelector)) + .length; + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + numOptions - 1 + ); + } +); + +ariaTest( + 'Test up key press with focus on listbox', + exampleFile, + 'listbox-key-up-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_UP to textbox to textbox to put focus in textbox // Up arrow should move selection to the last item in the list await t.context.session @@ -328,11 +447,15 @@ ariaTest('Test up key press with focus on listbox', // Account for race condition await waitForFocusChange(t, ex.textboxSelector, ''); - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, ex.numAOptions-1); + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + ex.numAOptions - 1 + ); // Test that ARROW_UP moves active descendant focus up one item in the listbox - for (let index = ex.numAOptions - 2; index > 0 ; index--) { - + for (let index = ex.numAOptions - 2; index > 0; index--) { let oldfocus = await t.context.session .findElement(By.css(ex.textboxSelector)) .getAttribute('aria-activedescendant'); @@ -344,15 +467,21 @@ ariaTest('Test up key press with focus on listbox', await waitForFocusChange(t, ex.textboxSelector, oldfocus); - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, index); + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + index + ); } - }); - - -ariaTest('Test enter key press with focus on textbox', - exampleFile, 'textbox-key-enter', async (t) => { - - + } +); + +ariaTest( + 'Test enter key press with focus on textbox', + exampleFile, + 'textbox-key-enter', + async (t) => { // Send key "a" to the textbox await t.context.session @@ -361,7 +490,9 @@ ariaTest('Test enter key press with focus on textbox', // Get the value of the first option in the listbox - const firstOption = await t.context.session.findElement(By.css(ex.optionsSelector)).getText(); + const firstOption = await t.context.session + .findElement(By.css(ex.optionsSelector)) + .getText(); // Send key ENTER @@ -371,7 +502,12 @@ ariaTest('Test enter key press with focus on textbox', // Confirm that the listbox is still open - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); // Confirm that the value of the textbox is now set to the first option @@ -382,12 +518,14 @@ ariaTest('Test enter key press with focus on textbox', firstOption, 'key press "ENTER" should result in first option in textbox' ); - }); - -ariaTest('Test enter key press with focus on listbox', - exampleFile, 'listbox-key-enter', async (t) => { - - + } +); + +ariaTest( + 'Test enter key press with focus on listbox', + exampleFile, + 'listbox-key-enter', + async (t) => { // Send key "a" to the textbox, then two ARROW_DOWNS await t.context.session @@ -396,8 +534,9 @@ ariaTest('Test enter key press with focus on listbox', // Get the value of the first option in the listbox - const secondOption = await(await t.context.queryElements(t, ex.optionsSelector))[1] - .getText(); + const secondOption = await ( + await t.context.queryElements(t, ex.optionsSelector) + )[1].getText(); // Send key ENTER @@ -407,7 +546,12 @@ ariaTest('Test enter key press with focus on listbox', // Confirm that the listbox is still open - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); // Confirm that the value of the textbox is now set to the second option @@ -418,13 +562,14 @@ ariaTest('Test enter key press with focus on listbox', secondOption, 'key press "ENTER" should result in second option in textbox' ); - - }); - - -ariaTest('Test single escape key press with focus on textbox', - exampleFile, 'textbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test single escape key press with focus on textbox', + exampleFile, + 'textbox-key-escape', + async (t) => { // Send key "a", then key ESCAPE once to the textbox await t.context.session @@ -433,7 +578,12 @@ ariaTest('Test single escape key press with focus on textbox', // Confirm the listbox is closed and the textbox is not cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -441,12 +591,14 @@ ariaTest('Test single escape key press with focus on textbox', 'Alabama', 'In key press "ESCAPE" should result in first option in textbox' ); - - }); - -ariaTest('Test double escape key press with focus on textbox', - exampleFile, 'textbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test double escape key press with focus on textbox', + exampleFile, + 'textbox-key-escape', + async (t) => { // Send key "a", then key ESCAPE twice to the textbox await t.context.session @@ -455,7 +607,12 @@ ariaTest('Test double escape key press with focus on textbox', // Confirm the listbox is closed and the textbox is cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -463,12 +620,14 @@ ariaTest('Test double escape key press with focus on textbox', '', 'In key press "ESCAPE" should result in first option in textbox' ); - - }); - -ariaTest('Test escape key press with focus on textbox', - exampleFile, 'listbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test escape key press with focus on textbox', + exampleFile, + 'listbox-key-escape', + async (t) => { // Send key "a" then key "ARROW_DOWN to put the focus on the listbox, // then key ESCAPE to the textbox @@ -478,7 +637,12 @@ ariaTest('Test escape key press with focus on textbox', // Confirm the listbox is closed and the textboxed is cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -486,37 +650,51 @@ ariaTest('Test escape key press with focus on textbox', ex.secondAOption, 'In listbox key press "ESCAPE" should result in second option in textbox' ); - - }); - -ariaTest('left arrow from focus on list puts focus on listbox and moves cursor right', - exampleFile, 'listbox-key-left-arrow', async (t) => { - + } +); + +ariaTest( + 'left arrow from focus on list puts focus on listbox and moves cursor right', + exampleFile, + 'listbox-key-left-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "ARROW_LEFT" await textbox.sendKeys(Key.ARROW_LEFT); t.true( - await confirmCursorIndex(t, ex.textboxSelector, ex.secondAOption.length - 1), - 'Cursor should be at index ' + (ex.secondAOption.length - 1) + ' after one ARROW_LEFT key' + await confirmCursorIndex( + t, + ex.textboxSelector, + ex.secondAOption.length - 1 + ), + 'Cursor should be at index ' + + (ex.secondAOption.length - 1) + + ' after one ARROW_LEFT key' ); t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_LEFT key', + 'Focus should be on the textbox after on ARROW_LEFT key' ); - }); - - -ariaTest('Right arrow from focus on list puts focus on listbox', - exampleFile, 'listbox-key-right-arrow', async (t) => { - + } +); + +ariaTest( + 'Right arrow from focus on list puts focus on listbox', + exampleFile, + 'listbox-key-right-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "RIGHT_ARROW" @@ -524,21 +702,28 @@ ariaTest('Right arrow from focus on list puts focus on listbox', t.true( await confirmCursorIndex(t, ex.textboxSelector, ex.secondAOption.length), - 'Cursor should be at index ' + ex.secondAOption.length + ' after one ARROW_RIGHT key' + 'Cursor should be at index ' + + ex.secondAOption.length + + ' after one ARROW_RIGHT key' ); t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_RIGHT key', + 'Focus should be on the textbox after on ARROW_RIGHT key' ); - }); - -ariaTest('Home from focus on list puts focus on listbox and moves cursor', - exampleFile, 'listbox-key-home', async (t) => { - + } +); + +ariaTest( + 'Home from focus on list puts focus on listbox and moves cursor', + exampleFile, + 'listbox-key-home', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "ARROW_HOME" @@ -552,15 +737,20 @@ ariaTest('Home from focus on list puts focus on listbox and moves cursor', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after one ARROW_HOME key', + 'Focus should be on the textbox after one ARROW_HOME key' ); - }); - -ariaTest('End from focus on list puts focus on listbox', - exampleFile, 'listbox-key-end', async (t) => { - + } +); + +ariaTest( + 'End from focus on list puts focus on listbox', + exampleFile, + 'listbox-key-end', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "END_ARROW" @@ -568,28 +758,37 @@ ariaTest('End from focus on list puts focus on listbox', t.true( await confirmCursorIndex(t, ex.textboxSelector, ex.secondAOption.length), - 'Cursor should be at index ' + ex.secondAOption.length + ' after one ARROW_END key' + 'Cursor should be at index ' + + ex.secondAOption.length + + ' after one ARROW_END key' ); t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_END key', + 'Focus should be on the textbox after on ARROW_END key' ); - }); - -ariaTest('Sending character keys while focus is on listbox moves focus', - exampleFile, 'listbox-characters', async (t) => { - + } +); + +ariaTest( + 'Sending character keys while focus is on listbox moves focus', + exampleFile, + 'listbox-characters', + async (t) => { // Send key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys(Key.ARROW_DOWN); // Send key "a" await textbox.sendKeys('a'); // Get the value of the first option in the listbox - const firstOption = await t.context.session.findElement(By.css(ex.optionsSelector)).getText(); + const firstOption = await t.context.session + .findElement(By.css(ex.optionsSelector)) + .getText(); t.is( await textbox.getAttribute('value'), @@ -601,16 +800,20 @@ ariaTest('Sending character keys while focus is on listbox moves focus', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after sending a character key while the focus is on the listbox', + 'Focus should be on the textbox after sending a character key while the focus is on the listbox' ); - - }); - -ariaTest('Expected behavior for all other standard single line editing keys', - exampleFile, 'standard-single-line-editing-keys', async (t) => { - + } +); + +ariaTest( + 'Expected behavior for all other standard single line editing keys', + exampleFile, + 'standard-single-line-editing-keys', + async (t) => { // Send key "a" - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a'); t.is( @@ -618,4 +821,5 @@ ariaTest('Expected behavior for all other standard single line editing keys', ex.numAOptions, 'Sending standard editing keys should filter results' ); - }); + } +); diff --git a/test/tests/combobox_autocomplete-list.js b/test/tests/combobox_autocomplete-list.js index fe194eb5ec..30686b7afa 100644 --- a/test/tests/combobox_autocomplete-list.js +++ b/test/tests/combobox_autocomplete-list.js @@ -15,7 +15,7 @@ const ex = { listboxSelector: '#ex1 [role="listbox"]', optionsSelector: '#ex1 [role="option"]', buttonSelector: '#ex1 button', - numAOptions: 5 + numAOptions: 5, }; const waitForFocusChange = async (t, textboxSelector, originalFocus) => { @@ -27,209 +27,308 @@ const waitForFocusChange = async (t, textboxSelector, originalFocus) => { return newfocus != originalFocus; }, t.context.waitTime, - 'Error waiting for "aria-activedescendant" value to change from "' + originalFocus + '". ' + 'Error waiting for "aria-activedescendant" value to change from "' + + originalFocus + + '". ' ); }; const confirmCursorIndex = async (t, selector, cursorIndex) => { - return t.context.session.executeScript(function () { - const [selector, cursorIndex] = arguments; - let item = document.querySelector(selector); - return item.selectionStart === cursorIndex; - }, selector, cursorIndex); + return t.context.session.executeScript( + function () { + const [selector, cursorIndex] = arguments; + let item = document.querySelector(selector); + return item.selectionStart === cursorIndex; + }, + selector, + cursorIndex + ); }; // Attributes -ariaTest('Test for role="combobox"', exampleFile, 'combobox-role', async (t) => { +ariaTest( + 'Test for role="combobox"', + exampleFile, + 'combobox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'combobox', '1', 'input'); -}); - -ariaTest('"aria-autocomplete" on comboxbox element', exampleFile, 'combobox-aria-autocomplete', async (t) => { - await assertAttributeValues(t, ex.textboxSelector, 'aria-autocomplete', 'list'); -}); - -ariaTest('"aria-controls" attribute on combobox element', exampleFile, 'combobox-aria-controls', async (t) => { - - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); - - t.truthy( - popupId, - '"aria-controls" attribute should exist on: ' + ex.textboxSelector - ); - - const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - - t.is( - popupElements.length, - 1, - 'There should be a element with id "' + popupId + '" as referenced by the aria-controls attribute' - ); -}); - -ariaTest('"aria-expanded" on combobox element', exampleFile, 'combobox-aria-expanded', async (t) => { + } +); + +ariaTest( + '"aria-autocomplete" on comboxbox element', + exampleFile, + 'combobox-aria-autocomplete', + async (t) => { + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-autocomplete', + 'list' + ); + } +); + +ariaTest( + '"aria-controls" attribute on combobox element', + exampleFile, + 'combobox-aria-controls', + async (t) => { + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - const combobox = await t.context.session.findElement(By.css(ex.textboxSelector)); + t.truthy( + popupId, + '"aria-controls" attribute should exist on: ' + ex.textboxSelector + ); - // Check that aria-expanded is false and the listbox is not visible before interacting + const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - t.is( - await combobox.getAttribute('aria-expanded'), - 'false', - 'combobox element should have attribute "aria-expanded" set to false by default.' - ); - - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); + t.is( + popupElements.length, + 1, + 'There should be a element with id "' + + popupId + + '" as referenced by the aria-controls attribute' + ); + } +); + +ariaTest( + '"aria-expanded" on combobox element', + exampleFile, + 'combobox-aria-expanded', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); - const popupElement = await t.context.session - .findElement(By.id('ex1')) - .findElement(By.id(popupId)); + // Check that aria-expanded is false and the listbox is not visible before interacting - t.false( - await popupElement.isDisplayed(), - 'Popup element should not be displayed when \'aria-expanded\' is false\'' - ); + t.is( + await combobox.getAttribute('aria-expanded'), + 'false', + 'combobox element should have attribute "aria-expanded" set to false by default.' + ); - // Send key "a" to textbox + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - await t.context.session - .findElement(By.css(ex.textboxSelector)) - .sendKeys('a'); + const popupElement = await t.context.session + .findElement(By.id('ex1')) + .findElement(By.id(popupId)); - // Check that aria-expanded is true and the listbox is visible + t.false( + await popupElement.isDisplayed(), + "Popup element should not be displayed when 'aria-expanded' is false'" + ); - t.is( - await combobox.getAttribute('aria-expanded'), - 'true', - 'combobox element should have attribute "aria-expand" set to true after typing.' - ); + // Send key "a" to textbox - t.true( - await popupElement.isDisplayed(), - 'Popup element should be displayed when \'aria-expanded\' is true\'' - ); + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); -}); + // Check that aria-expanded is true and the listbox is visible -ariaTest('"aria-activedescendant" on combobox element', exampleFile, 'combobox-aria-activedescendant', async (t) => { - await assertAttributeValues(t, ex.textboxSelector, 'aria-activedescendant', null); -}); + t.is( + await combobox.getAttribute('aria-expanded'), + 'true', + 'combobox element should have attribute "aria-expand" set to true after typing.' + ); -ariaTest('role "listbox" on ul element', exampleFile, 'listbox-role', async (t) => { + t.true( + await popupElement.isDisplayed(), + "Popup element should be displayed when 'aria-expanded' is true'" + ); + } +); + +ariaTest( + '"aria-activedescendant" on combobox element', + exampleFile, + 'combobox-aria-activedescendant', + async (t) => { + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-activedescendant', + null + ); + } +); + +ariaTest( + 'role "listbox" on ul element', + exampleFile, + 'listbox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'listbox', '1', 'ul'); -}); - -ariaTest('"aria-label" attribute on listbox element', exampleFile, 'listbox-aria-label', async (t) => { + } +); + +ariaTest( + '"aria-label" attribute on listbox element', + exampleFile, + 'listbox-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.listboxSelector); -}); - -ariaTest('role "option" on lu elements', exampleFile, 'option-role', async (t) => { - - // Send arrow down to reveal all options - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys(Key.ARROW_DOWN); - await assertAriaRoles(t, 'ex1', 'option', '56', 'li'); -}); - -ariaTest('"aria-selected" attribute on options element', exampleFile, 'option-aria-selected', async (t) => { - - // Send key "a" - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys('a'); - await assertAttributeDNE(t, ex.optionsSelector + ':nth-of-type(1)', 'aria-selected'); - - // Send key ARROW_DOWN to selected first option - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys(Key.ARROW_DOWN); - await assertAttributeValues(t, ex.optionsSelector + ':nth-of-type(1)', 'aria-selected', 'true'); -}); - -ariaTest('Button should have tabindex="-1"', exampleFile, 'button-tabindex', async (t) => { - const button = await t.context.session.findElement(By.css(ex.buttonSelector)) - - t.is( - await button.getAttribute('tabindex'), - '-1', - 'tabindex should be set to "-1" on button' - ); -}); - -ariaTest('"aria-label" attribute on button element', exampleFile, 'button-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.buttonSelector); -}); - -ariaTest('"aria-controls" attribute on button element', exampleFile, 'button-aria-controls', async (t) => { - const popupId = await t.context.session - .findElement(By.css(ex.buttonSelector)) - .getAttribute('aria-controls'); - - t.truthy( - popupId, - '"aria-controls" attribute should exist on: ' + ex.buttonSelector - ); - - const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - - t.is( - popupElements.length, - 1, - 'There should be a element with id "' + popupId + '" as referenced by the aria-controls attribute' - ); -}); + } +); + +ariaTest( + 'role "option" on lu elements', + exampleFile, + 'option-role', + async (t) => { + // Send arrow down to reveal all options + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys(Key.ARROW_DOWN); + await assertAriaRoles(t, 'ex1', 'option', '56', 'li'); + } +); + +ariaTest( + '"aria-selected" attribute on options element', + exampleFile, + 'option-aria-selected', + async (t) => { + // Send key "a" + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); + await assertAttributeDNE( + t, + ex.optionsSelector + ':nth-of-type(1)', + 'aria-selected' + ); + // Send key ARROW_DOWN to selected first option + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys(Key.ARROW_DOWN); + await assertAttributeValues( + t, + ex.optionsSelector + ':nth-of-type(1)', + 'aria-selected', + 'true' + ); + } +); + +ariaTest( + 'Button should have tabindex="-1"', + exampleFile, + 'button-tabindex', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); -ariaTest('"aria-expanded" on button element', exampleFile, 'button-aria-expanded', async (t) => { - const button = await t.context.session.findElement(By.css(ex.buttonSelector)); + t.is( + await button.getAttribute('tabindex'), + '-1', + 'tabindex should be set to "-1" on button' + ); + } +); + +ariaTest( + '"aria-label" attribute on button element', + exampleFile, + 'button-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.buttonSelector); + } +); + +ariaTest( + '"aria-controls" attribute on button element', + exampleFile, + 'button-aria-controls', + async (t) => { + const popupId = await t.context.session + .findElement(By.css(ex.buttonSelector)) + .getAttribute('aria-controls'); + + t.truthy( + popupId, + '"aria-controls" attribute should exist on: ' + ex.buttonSelector + ); - // Check that aria-expanded is false and the listbox is not visible before interacting + const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - t.is( - await button.getAttribute('aria-expanded'), - 'false', - 'button element should have attribute "aria-expanded" set to false by default.' - ); + t.is( + popupElements.length, + 1, + 'There should be a element with id "' + + popupId + + '" as referenced by the aria-controls attribute' + ); + } +); + +ariaTest( + '"aria-expanded" on button element', + exampleFile, + 'button-aria-expanded', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); + // Check that aria-expanded is false and the listbox is not visible before interacting - const popupElement = await t.context.session - .findElement(By.id('ex1')) - .findElement(By.id(popupId)); + t.is( + await button.getAttribute('aria-expanded'), + 'false', + 'button element should have attribute "aria-expanded" set to false by default.' + ); - t.false( - await popupElement.isDisplayed(), - 'Popup element should not be displayed when \'aria-expanded\' is false\'' - ); + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - // Send key "a" to textbox + const popupElement = await t.context.session + .findElement(By.id('ex1')) + .findElement(By.id(popupId)); - await t.context.session - .findElement(By.css(ex.textboxSelector)) - .sendKeys('a'); + t.false( + await popupElement.isDisplayed(), + "Popup element should not be displayed when 'aria-expanded' is false'" + ); - // Check that aria-expanded is true and the listbox is visible + // Send key "a" to textbox - t.is( - await button.getAttribute('aria-expanded'), - 'true', - 'button element should have attribute "aria-expand" set to true after typing.' - ); + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); - t.true( - await popupElement.isDisplayed(), - 'Popup element should be displayed when \'aria-expanded\' is true\'' - ); + // Check that aria-expanded is true and the listbox is visible -}); + t.is( + await button.getAttribute('aria-expanded'), + 'true', + 'button element should have attribute "aria-expand" set to true after typing.' + ); + t.true( + await popupElement.isDisplayed(), + "Popup element should be displayed when 'aria-expanded' is true'" + ); + } +); // Keys -ariaTest('Test alt + down key press with focus on textbox', - exampleFile, 'textbox-key-alt-down-arrow', async (t) => { - - +ariaTest( + 'Test alt + down key press with focus on textbox', + exampleFile, + 'textbox-key-alt-down-arrow', + async (t) => { // Send ARROW_DOWN to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -237,19 +336,22 @@ ariaTest('Test alt + down key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example the list box should display after ALT + ARROW_DOWN keypress' ); // aria-selected should not be on any options await assertAttributeDNE(t, ex.optionsSelector, 'aria-selected'); - - }); - -ariaTest('Test down key press with focus on textbox', - exampleFile, 'textbox-key-down-arrow', async (t) => { - - + } +); + +ariaTest( + 'Test down key press with focus on textbox', + exampleFile, + 'textbox-key-down-arrow', + async (t) => { // Send ARROW_DOWN to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -257,28 +359,36 @@ ariaTest('Test down key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example ex3 listbox should display after ARROW_DOWN keypress' ); await waitForFocusChange(t, ex.textboxSelector, null); // Check that the active descendent focus is correct - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, 0); - - }); - -ariaTest('Test down key press with focus on list', - exampleFile, 'listbox-key-down-arrow', async (t) => { - - + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + 0 + ); + } +); + +ariaTest( + 'Test down key press with focus on list', + exampleFile, + 'listbox-key-down-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_DOWN to textbox to set focus on listbox await t.context.session .findElement(By.css(ex.textboxSelector)) .sendKeys('a', Key.ARROW_DOWN); // Test that ARROW_DOWN moves active descendant focus on item in listbox - for (let i = 1; i < ex.numAOptions; i++) { + for (let i = 1; i < ex.numAOptions; i++) { let oldfocus = await t.context.session .findElement(By.css(ex.textboxSelector)) .getAttribute('aria-activedescendant'); @@ -290,7 +400,12 @@ ariaTest('Test down key press with focus on list', // Account for race condition await waitForFocusChange(t, ex.textboxSelector, oldfocus); - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, i); + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + i + ); } // Sending ARROW_DOWN to the last item should put focus on the first @@ -305,15 +420,20 @@ ariaTest('Test down key press with focus on list', await waitForFocusChange(t, ex.textboxSelector, oldfocus); // Focus should be on the first item - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, 0); - - }); - - -ariaTest('Test up key press with focus on textbox', - exampleFile, 'textbox-key-up-arrow', async (t) => { - - + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + 0 + ); + } +); + +ariaTest( + 'Test up key press with focus on textbox', + exampleFile, + 'textbox-key-up-arrow', + async (t) => { // Send ARROW_UP to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -321,21 +441,31 @@ ariaTest('Test up key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example ex3 listbox should display after ARROW_UP keypress' ); await waitForFocusChange(t, ex.textboxSelector, null); // Check that the active descendent focus is correct - let numOptions = (await t.context.queryElements(t, ex.optionsSelector)).length; - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, numOptions - 1); - }); - -ariaTest('Test up key press with focus on listbox', - exampleFile, 'listbox-key-up-arrow', async (t) => { - - + let numOptions = (await t.context.queryElements(t, ex.optionsSelector)) + .length; + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + numOptions - 1 + ); + } +); + +ariaTest( + 'Test up key press with focus on listbox', + exampleFile, + 'listbox-key-up-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_UP to textbox to textbox to put focus in textbox // Up arrow should move selection to the last item in the list await t.context.session @@ -343,7 +473,7 @@ ariaTest('Test up key press with focus on listbox', .sendKeys('a', Key.ARROW_UP); // Test that ARROW_UP moves active descendant focus up one item in the listbox - for (let index = ex.numAOptions - 2; index > 0 ; index--) { + for (let index = ex.numAOptions - 2; index > 0; index--) { let oldfocus = await t.context.session .findElement(By.css(ex.textboxSelector)) .getAttribute('aria-activedescendant'); @@ -355,14 +485,21 @@ ariaTest('Test up key press with focus on listbox', await waitForFocusChange(t, ex.textboxSelector, oldfocus); - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, index); + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + index + ); } - }); - -ariaTest('Test enter key press with focus on textbox', - exampleFile, 'textbox-key-enter', async (t) => { - - + } +); + +ariaTest( + 'Test enter key press with focus on textbox', + exampleFile, + 'textbox-key-enter', + async (t) => { // Send key "a" to the textbox, then key ARROW_DOWN to select the first item await t.context.session @@ -377,7 +514,12 @@ ariaTest('Test enter key press with focus on textbox', // Confirm that the listbox is closed - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); // Confirm that the value of the textbox is the same as the characters set to the listbox @@ -388,13 +530,14 @@ ariaTest('Test enter key press with focus on textbox', 'a', 'key press "ENTER" should not result in selecting an option' ); - - }); - -ariaTest('Test enter key press with focus on listbox', - exampleFile, 'listbox-key-enter', async (t) => { - - + } +); + +ariaTest( + 'Test enter key press with focus on listbox', + exampleFile, + 'listbox-key-enter', + async (t) => { // Send key "a" to the textbox, then key ARROW_DOWN to select the first item await t.context.session @@ -403,7 +546,9 @@ ariaTest('Test enter key press with focus on listbox', // Get the value of the first option in the listbox - const firstOption = await t.context.session.findElement(By.css(ex.optionsSelector)).getText(); + const firstOption = await t.context.session + .findElement(By.css(ex.optionsSelector)) + .getText(); // Send key ENTER @@ -413,7 +558,12 @@ ariaTest('Test enter key press with focus on listbox', // Confirm that the listbox is closed - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); // Confirm that the value of the textbox is now set to the first option @@ -424,12 +574,14 @@ ariaTest('Test enter key press with focus on listbox', firstOption, 'key press "ENTER" should result in first option in textbox' ); - - }); - -ariaTest('Test single escape key press with focus on textbox', - exampleFile, 'textbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test single escape key press with focus on textbox', + exampleFile, + 'textbox-key-escape', + async (t) => { // Send key "a", then key ESCAPE once to the textbox await t.context.session @@ -438,7 +590,12 @@ ariaTest('Test single escape key press with focus on textbox', // Confirm the listbox is closed and the textbox is not cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -446,12 +603,14 @@ ariaTest('Test single escape key press with focus on textbox', 'a', 'In key press "ESCAPE" should result in first option in textbox' ); - - }); - -ariaTest('Test double escape key press with focus on textbox', - exampleFile, 'textbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test double escape key press with focus on textbox', + exampleFile, + 'textbox-key-escape', + async (t) => { // Send key "a", then key ESCAPE twice to the textbox await t.context.session @@ -460,7 +619,12 @@ ariaTest('Test double escape key press with focus on textbox', // Confirm the listbox is closed and the textbox is cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -468,12 +632,14 @@ ariaTest('Test double escape key press with focus on textbox', '', 'In key press "ESCAPE" should result in first option in textbox' ); - - }); - -ariaTest('Test escape key press with focus on textbox', - exampleFile, 'listbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test escape key press with focus on textbox', + exampleFile, + 'listbox-key-escape', + async (t) => { // Send key "a" then key "ARROW_DOWN to put the focus on the listbox, // then key ESCAPE to the textbox @@ -483,7 +649,12 @@ ariaTest('Test escape key press with focus on textbox', // Confirm the listbox is closed and the textboxed is cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -491,14 +662,18 @@ ariaTest('Test escape key press with focus on textbox', 'a', 'In listbox key press "ESCAPE" should result in first option in textbox' ); - - }); - -ariaTest('left arrow from focus on list puts focus on listbox and moves cursor right', - exampleFile, 'listbox-key-left-arrow', async (t) => { - + } +); + +ariaTest( + 'left arrow from focus on list puts focus on listbox and moves cursor right', + exampleFile, + 'listbox-key-left-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "ARROW_LEFT" @@ -512,16 +687,20 @@ ariaTest('left arrow from focus on list puts focus on listbox and moves cursor r t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_LEFT key', + 'Focus should be on the textbox after on ARROW_LEFT key' ); - }); - - -ariaTest('Right arrow from focus on list puts focus on listbox', - exampleFile, 'listbox-key-right-arrow', async (t) => { - + } +); + +ariaTest( + 'Right arrow from focus on list puts focus on listbox', + exampleFile, + 'listbox-key-right-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "RIGHT_ARROW" @@ -535,15 +714,20 @@ ariaTest('Right arrow from focus on list puts focus on listbox', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_RIGHT key', + 'Focus should be on the textbox after on ARROW_RIGHT key' ); - }); - -ariaTest('Home from focus on list puts focus on listbox and moves cursor', - exampleFile, 'listbox-key-home', async (t) => { - + } +); + +ariaTest( + 'Home from focus on list puts focus on listbox and moves cursor', + exampleFile, + 'listbox-key-home', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "ARROW_HOME" @@ -557,15 +741,20 @@ ariaTest('Home from focus on list puts focus on listbox and moves cursor', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after one ARROW_HOME key', + 'Focus should be on the textbox after one ARROW_HOME key' ); - }); - -ariaTest('End from focus on list puts focus on listbox', - exampleFile, 'listbox-key-end', async (t) => { - + } +); + +ariaTest( + 'End from focus on list puts focus on listbox', + exampleFile, + 'listbox-key-end', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "END_ARROW" @@ -579,15 +768,20 @@ ariaTest('End from focus on list puts focus on listbox', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_END key', + 'Focus should be on the textbox after on ARROW_END key' ); - }); - -ariaTest('Sending character keys while focus is on listbox moves focus', - exampleFile, 'listbox-key-char', async (t) => { - + } +); + +ariaTest( + 'Sending character keys while focus is on listbox moves focus', + exampleFile, + 'listbox-key-char', + async (t) => { // Send key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys(Key.ARROW_DOWN); // Send key "a" @@ -603,16 +797,20 @@ ariaTest('Sending character keys while focus is on listbox moves focus', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after sending a character key while the focus is on the listbox', + 'Focus should be on the textbox after sending a character key while the focus is on the listbox' ); - - }); - -ariaTest('Expected behavior for all other standard single line editing keys', - exampleFile, 'standard-single-line-editing-keys', async (t) => { - + } +); + +ariaTest( + 'Expected behavior for all other standard single line editing keys', + exampleFile, + 'standard-single-line-editing-keys', + async (t) => { // Send key "a" - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a'); t.is( @@ -620,5 +818,5 @@ ariaTest('Expected behavior for all other standard single line editing keys', ex.numAOptions, 'Sending standard editing keys should filter results' ); - }); - + } +); diff --git a/test/tests/combobox_autocomplete-none.js b/test/tests/combobox_autocomplete-none.js index 0ab945d1e7..42af4b1e05 100644 --- a/test/tests/combobox_autocomplete-none.js +++ b/test/tests/combobox_autocomplete-none.js @@ -15,7 +15,7 @@ const ex = { listboxSelector: '#ex1 [role="listbox"]', optionsSelector: '#ex1 [role="option"]', buttonSelector: '#ex1 button', - numOptions: 11 + numOptions: 11, }; const waitForFocusChange = async (t, textboxSelector, originalFocus) => { @@ -27,207 +27,308 @@ const waitForFocusChange = async (t, textboxSelector, originalFocus) => { return newfocus != originalFocus; }, t.context.waitTime, - 'Timeout waiting for "aria-activedescendant" value to change from "' + originalFocus + '". ' + 'Timeout waiting for "aria-activedescendant" value to change from "' + + originalFocus + + '". ' ); }; const confirmCursorIndex = async (t, selector, cursorIndex) => { - return t.context.session.executeScript(function () { - const [selector, cursorIndex] = arguments; - let item = document.querySelector(selector); - return item.selectionStart === cursorIndex; - }, selector, cursorIndex); + return t.context.session.executeScript( + function () { + const [selector, cursorIndex] = arguments; + let item = document.querySelector(selector); + return item.selectionStart === cursorIndex; + }, + selector, + cursorIndex + ); }; // Attributes -ariaTest('Test for role="combobox"', exampleFile, 'combobox-role', async (t) => { +ariaTest( + 'Test for role="combobox"', + exampleFile, + 'combobox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'combobox', '1', 'input'); -}); - -ariaTest('"aria-autocomplete" on comboxbox element', exampleFile, 'combobox-aria-autocomplete', async (t) => { - await assertAttributeValues(t, ex.textboxSelector, 'aria-autocomplete', 'none'); -}); - -ariaTest('"aria-controls" attribute on combobox element', exampleFile, 'combobox-aria-controls', async (t) => { - - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); - - t.truthy( - popupId, - '"aria-controls" attribute should exist on: ' + ex.textboxSelector - ); - - const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - - t.is( - popupElements.length, - 1, - 'There should be a element with id "' + popupId + '" as referenced by the aria-controls attribute' - ); -}); - -ariaTest('"aria-expanded" on combobox element', exampleFile, 'combobox-aria-expanded', async (t) => { - - const combobox = await t.context.session.findElement(By.css(ex.textboxSelector)); + } +); + +ariaTest( + '"aria-autocomplete" on comboxbox element', + exampleFile, + 'combobox-aria-autocomplete', + async (t) => { + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-autocomplete', + 'none' + ); + } +); + +ariaTest( + '"aria-controls" attribute on combobox element', + exampleFile, + 'combobox-aria-controls', + async (t) => { + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - // Check that aria-expanded is false and the listbox is not visible before interacting + t.truthy( + popupId, + '"aria-controls" attribute should exist on: ' + ex.textboxSelector + ); - t.is( - await combobox.getAttribute('aria-expanded'), - 'false', - 'combobox element should have attribute "aria-expanded" set to false by default.' - ); + const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); + t.is( + popupElements.length, + 1, + 'There should be a element with id "' + + popupId + + '" as referenced by the aria-controls attribute' + ); + } +); + +ariaTest( + '"aria-expanded" on combobox element', + exampleFile, + 'combobox-aria-expanded', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); - const popupElement = await t.context.session - .findElement(By.id('ex1')) - .findElement(By.id(popupId)); + // Check that aria-expanded is false and the listbox is not visible before interacting - t.false( - await popupElement.isDisplayed(), - 'Popup element should not be displayed when \'aria-expanded\' is false\'' - ); + t.is( + await combobox.getAttribute('aria-expanded'), + 'false', + 'combobox element should have attribute "aria-expanded" set to false by default.' + ); - // Send key "a" to textbox + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - await t.context.session - .findElement(By.css(ex.textboxSelector)) - .sendKeys('a'); + const popupElement = await t.context.session + .findElement(By.id('ex1')) + .findElement(By.id(popupId)); - // Check that aria-expanded is true and the listbox is visible + t.false( + await popupElement.isDisplayed(), + "Popup element should not be displayed when 'aria-expanded' is false'" + ); - t.is( - await combobox.getAttribute('aria-expanded'), - 'true', - 'combobox element should have attribute "aria-expand" set to true after typing.' - ); + // Send key "a" to textbox - t.true( - await popupElement.isDisplayed(), - 'Popup element should be displayed when \'aria-expanded\' is true\'' - ); + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); -}); + // Check that aria-expanded is true and the listbox is visible -ariaTest('"aria-activedescendant" on combobox element', exampleFile, 'combobox-aria-activedescendant', async (t) => { - await assertAttributeValues(t, ex.textboxSelector, 'aria-activedescendant', null); -}); + t.is( + await combobox.getAttribute('aria-expanded'), + 'true', + 'combobox element should have attribute "aria-expand" set to true after typing.' + ); -ariaTest('role "listbox" on ul element', exampleFile, 'listbox-role', async (t) => { + t.true( + await popupElement.isDisplayed(), + "Popup element should be displayed when 'aria-expanded' is true'" + ); + } +); + +ariaTest( + '"aria-activedescendant" on combobox element', + exampleFile, + 'combobox-aria-activedescendant', + async (t) => { + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-activedescendant', + null + ); + } +); + +ariaTest( + 'role "listbox" on ul element', + exampleFile, + 'listbox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'listbox', '1', 'ul'); -}); - -ariaTest('"aria-label" attribute on listbox element', exampleFile, 'listbox-aria-label', async (t) => { + } +); + +ariaTest( + '"aria-label" attribute on listbox element', + exampleFile, + 'listbox-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.listboxSelector); -}); - -ariaTest('role "option" on lu elements', exampleFile, 'option-role', async (t) => { - - // Send arrow down to reveal all options - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys(Key.ARROW_DOWN); - await assertAriaRoles(t, 'ex1', 'option', ex.numOptions, 'li'); -}); - -ariaTest('"aria-selected" attribute on options element', exampleFile, 'option-aria-selected', async (t) => { - - // Send key "a" - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys('a'); - await assertAttributeDNE(t, ex.optionsSelector + ':nth-of-type(1)', 'aria-selected'); - - // Send key ARROW_DOWN to selected first option - await t.context.session.findElement(By.css(ex.textboxSelector)).sendKeys(Key.ARROW_DOWN); - await assertAttributeValues(t, ex.optionsSelector + ':nth-of-type(1)', 'aria-selected', 'true'); -}); - -ariaTest('Button should have tabindex="-1"', exampleFile, 'button-tabindex', async (t) => { - const button = await t.context.session.findElement(By.css(ex.buttonSelector)) - - t.is( - await button.getAttribute('tabindex'), - '-1', - 'tabindex should be set to "-1" on button' - ); -}); - -ariaTest('"aria-label" attribute on button element', exampleFile, 'button-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.buttonSelector); -}); - -ariaTest('"aria-controls" attribute on button element', exampleFile, 'button-aria-controls', async (t) => { - const popupId = await t.context.session - .findElement(By.css(ex.buttonSelector)) - .getAttribute('aria-controls'); - - t.truthy( - popupId, - '"aria-controls" attribute should exist on: ' + ex.buttonSelector - ); + } +); + +ariaTest( + 'role "option" on lu elements', + exampleFile, + 'option-role', + async (t) => { + // Send arrow down to reveal all options + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys(Key.ARROW_DOWN); + await assertAriaRoles(t, 'ex1', 'option', ex.numOptions, 'li'); + } +); + +ariaTest( + '"aria-selected" attribute on options element', + exampleFile, + 'option-aria-selected', + async (t) => { + // Send key "a" + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); + await assertAttributeDNE( + t, + ex.optionsSelector + ':nth-of-type(1)', + 'aria-selected' + ); - const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); + // Send key ARROW_DOWN to selected first option + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys(Key.ARROW_DOWN); + await assertAttributeValues( + t, + ex.optionsSelector + ':nth-of-type(1)', + 'aria-selected', + 'true' + ); + } +); + +ariaTest( + 'Button should have tabindex="-1"', + exampleFile, + 'button-tabindex', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); - t.is( - popupElements.length, - 1, - 'There should be a element with id "' + popupId + '" as referenced by the aria-controls attribute' - ); -}); + t.is( + await button.getAttribute('tabindex'), + '-1', + 'tabindex should be set to "-1" on button' + ); + } +); + +ariaTest( + '"aria-label" attribute on button element', + exampleFile, + 'button-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.buttonSelector); + } +); + +ariaTest( + '"aria-controls" attribute on button element', + exampleFile, + 'button-aria-controls', + async (t) => { + const popupId = await t.context.session + .findElement(By.css(ex.buttonSelector)) + .getAttribute('aria-controls'); + + t.truthy( + popupId, + '"aria-controls" attribute should exist on: ' + ex.buttonSelector + ); -ariaTest('"aria-expanded" on button element', exampleFile, 'button-aria-expanded', async (t) => { - const button = await t.context.session.findElement(By.css(ex.buttonSelector)); + const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); - // Check that aria-expanded is false and the listbox is not visible before interacting + t.is( + popupElements.length, + 1, + 'There should be a element with id "' + + popupId + + '" as referenced by the aria-controls attribute' + ); + } +); + +ariaTest( + '"aria-expanded" on button element', + exampleFile, + 'button-aria-expanded', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); - t.is( - await button.getAttribute('aria-expanded'), - 'false', - 'button element should have attribute "aria-expanded" set to false by default.' - ); + // Check that aria-expanded is false and the listbox is not visible before interacting - const popupId = await t.context.session - .findElement(By.css(ex.textboxSelector)) - .getAttribute('aria-controls'); + t.is( + await button.getAttribute('aria-expanded'), + 'false', + 'button element should have attribute "aria-expanded" set to false by default.' + ); - const popupElement = await t.context.session - .findElement(By.id('ex1')) - .findElement(By.id(popupId)); + const popupId = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('aria-controls'); - t.false( - await popupElement.isDisplayed(), - 'Popup element should not be displayed when \'aria-expanded\' is \'false\'' - ); + const popupElement = await t.context.session + .findElement(By.id('ex1')) + .findElement(By.id(popupId)); - // Send key "a" to textbox + t.false( + await popupElement.isDisplayed(), + "Popup element should not be displayed when 'aria-expanded' is 'false'" + ); - await t.context.session - .findElement(By.css(ex.textboxSelector)) - .sendKeys('a'); + // Send key "a" to textbox - // Check that aria-expanded is true and the listbox is visible + await t.context.session + .findElement(By.css(ex.textboxSelector)) + .sendKeys('a'); - t.is( - await button.getAttribute('aria-expanded'), - 'true', - 'button element should have attribute "aria-expand" set to true after typing.' - ); + // Check that aria-expanded is true and the listbox is visible - t.true( - await popupElement.isDisplayed(), - 'Popup element should be displayed when \'aria-expanded\' is \'true\'' - ); + t.is( + await button.getAttribute('aria-expanded'), + 'true', + 'button element should have attribute "aria-expand" set to true after typing.' + ); -}); + t.true( + await popupElement.isDisplayed(), + "Popup element should be displayed when 'aria-expanded' is 'true'" + ); + } +); // Keys -ariaTest('Test alt + down key press with focus on textbox', - exampleFile, 'textbox-key-alt-down-arrow', async (t) => { - - +ariaTest( + 'Test alt + down key press with focus on textbox', + exampleFile, + 'textbox-key-alt-down-arrow', + async (t) => { // Send ARROW_DOWN to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -235,20 +336,22 @@ ariaTest('Test alt + down key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example the list box should display after ALT + ARROW_DOWN keypress' ); // aria-selected should not be on any options await assertAttributeDNE(t, ex.optionsSelector, 'aria-selected'); - - }); - - -ariaTest('Test down key press with focus on textbox', - exampleFile, 'textbox-key-down-arrow', async (t) => { - - + } +); + +ariaTest( + 'Test down key press with focus on textbox', + exampleFile, + 'textbox-key-down-arrow', + async (t) => { // Send ARROW_DOWN to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -256,28 +359,36 @@ ariaTest('Test down key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example ex3 listbox should display after ARROW_DOWN keypress' ); await waitForFocusChange(t, ex.textboxSelector, null); // Check that the active descendent focus is correct - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, 0); - - }); - -ariaTest('Test down key press with focus on list', - exampleFile, 'listbox-key-down-arrow', async (t) => { - - + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + 0 + ); + } +); + +ariaTest( + 'Test down key press with focus on list', + exampleFile, + 'listbox-key-down-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_DOWN to textbox to set focus on listbox await t.context.session .findElement(By.css(ex.textboxSelector)) .sendKeys('a', Key.ARROW_DOWN); // Test that ARROW_DOWN moves active descendant focus on item in listbox - for (let i = 1; i < ex.numOptions; i++) { + for (let i = 1; i < ex.numOptions; i++) { let oldfocus = await t.context.session .findElement(By.css(ex.textboxSelector)) .getAttribute('aria-activedescendant'); @@ -289,7 +400,12 @@ ariaTest('Test down key press with focus on list', // Account for race condition await waitForFocusChange(t, ex.textboxSelector, oldfocus); - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, i); + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + i + ); } // Sending ARROW_DOWN to the last item should put focus on the first @@ -304,15 +420,20 @@ ariaTest('Test down key press with focus on list', await waitForFocusChange(t, ex.textboxSelector, oldfocus); // Focus should be on the first item - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, 0); - - }); - - -ariaTest('Test up key press with focus on textbox', - exampleFile, 'textbox-key-up-arrow', async (t) => { - - + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + 0 + ); + } +); + +ariaTest( + 'Test up key press with focus on textbox', + exampleFile, + 'textbox-key-up-arrow', + async (t) => { // Send ARROW_UP to the textbox await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -320,21 +441,31 @@ ariaTest('Test up key press with focus on textbox', // Check that the listbox is displayed t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'In example ex3 listbox should display after ARROW_UP keypress' ); await waitForFocusChange(t, ex.textboxSelector, null); // Check that the active descendent focus is correct - let numOptions = (await t.context.queryElements(t, ex.optionsSelector)).length; - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, numOptions - 1); - }); - -ariaTest('Test up key press with focus on listbox', - exampleFile, 'listbox-key-up-arrow', async (t) => { - - + let numOptions = (await t.context.queryElements(t, ex.optionsSelector)) + .length; + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + numOptions - 1 + ); + } +); + +ariaTest( + 'Test up key press with focus on listbox', + exampleFile, + 'listbox-key-up-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_UP to textbox to textbox to put focus in textbox // Up arrow should move selection to the last item in the list await t.context.session @@ -342,7 +473,7 @@ ariaTest('Test up key press with focus on listbox', .sendKeys('a', Key.ARROW_UP); // Test that ARROW_UP moves active descendant focus up one item in the listbox - for (let index = ex.numOptions - 2; index > 0 ; index--) { + for (let index = ex.numOptions - 2; index > 0; index--) { let oldfocus = await t.context.session .findElement(By.css(ex.textboxSelector)) .getAttribute('aria-activedescendant'); @@ -354,14 +485,21 @@ ariaTest('Test up key press with focus on listbox', await waitForFocusChange(t, ex.textboxSelector, oldfocus); - await assertAriaSelectedAndActivedescendant(t, ex.textboxSelector, ex.optionsSelector, index); + await assertAriaSelectedAndActivedescendant( + t, + ex.textboxSelector, + ex.optionsSelector, + index + ); } - }); - -ariaTest('Test enter key press with focus on textbox', - exampleFile, 'textbox-key-enter', async (t) => { - - + } +); + +ariaTest( + 'Test enter key press with focus on textbox', + exampleFile, + 'textbox-key-enter', + async (t) => { // Send key "a" to the textbox, then key ARROW_DOWN to select the first item await t.context.session @@ -376,7 +514,12 @@ ariaTest('Test enter key press with focus on textbox', // Confirm that the listbox is closed - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); // Confirm that the value of the textbox is the same as the characters set to the listbox @@ -387,12 +530,14 @@ ariaTest('Test enter key press with focus on textbox', 'a', 'key press "ENTER" should not result in selecting an option' ); - }); - -ariaTest('Test enter key press with focus on listbox', - exampleFile, 'listbox-key-enter', async (t) => { - - + } +); + +ariaTest( + 'Test enter key press with focus on listbox', + exampleFile, + 'listbox-key-enter', + async (t) => { // Send key "a" to the textbox, then key ARROW_DOWN to select the first item await t.context.session @@ -401,7 +546,9 @@ ariaTest('Test enter key press with focus on listbox', // Get the value of the first option in the listbox - const firstOption = await t.context.session.findElement(By.css(ex.optionsSelector)).getText(); + const firstOption = await t.context.session + .findElement(By.css(ex.optionsSelector)) + .getText(); // Send key ENTER @@ -411,7 +558,12 @@ ariaTest('Test enter key press with focus on listbox', // Confirm that the listbox is note - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); // Confirm that the value of the textbox is now set to the first option @@ -422,12 +574,14 @@ ariaTest('Test enter key press with focus on listbox', firstOption, 'key press "ENTER" should result in first option in textbox' ); - - }); - -ariaTest('Test escape key press with focus on textbox', - exampleFile, 'listbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test escape key press with focus on textbox', + exampleFile, + 'listbox-key-escape', + async (t) => { // Send key "a" then key "ARROW_DOWN to put the focus on the listbox, // then key ESCAPE once to the textbox @@ -437,7 +591,12 @@ ariaTest('Test escape key press with focus on textbox', // Confirm the listbox is closed and the textbox is not cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -445,12 +604,14 @@ ariaTest('Test escape key press with focus on textbox', 'a', 'In listbox key press "ESCAPE" should result in first option in textbox' ); - - }); - -ariaTest('Test double escape key press with focus on textbox', - exampleFile, 'listbox-key-escape', async (t) => { - + } +); + +ariaTest( + 'Test double escape key press with focus on textbox', + exampleFile, + 'listbox-key-escape', + async (t) => { // Send key "a" then key "ARROW_DOWN to put the focus on the listbox, // then key ESCAPE twice to the textbox @@ -460,7 +621,12 @@ ariaTest('Test double escape key press with focus on textbox', // Confirm the listbox is closed and the textbox is cleared - await assertAttributeValues(t, ex.textboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.textboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.textboxSelector)) @@ -468,14 +634,18 @@ ariaTest('Test double escape key press with focus on textbox', '', 'In listbox key press "ESCAPE" should result in first option in textbox' ); - - }); - -ariaTest('left arrow from focus on list puts focus on listbox and moves cursor right', - exampleFile, 'listbox-key-left-arrow', async (t) => { - + } +); + +ariaTest( + 'left arrow from focus on list puts focus on listbox and moves cursor right', + exampleFile, + 'listbox-key-left-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "ARROW_LEFT" @@ -489,16 +659,20 @@ ariaTest('left arrow from focus on list puts focus on listbox and moves cursor r t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_LEFT key', + 'Focus should be on the textbox after on ARROW_LEFT key' ); - }); - - -ariaTest('Right arrow from focus on list puts focus on listbox', - exampleFile, 'listbox-key-right-arrow', async (t) => { - + } +); + +ariaTest( + 'Right arrow from focus on list puts focus on listbox', + exampleFile, + 'listbox-key-right-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "RIGHT_ARROW" @@ -512,15 +686,20 @@ ariaTest('Right arrow from focus on list puts focus on listbox', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_RIGHT key', + 'Focus should be on the textbox after on ARROW_RIGHT key' ); - }); - -ariaTest('Home from focus on list puts focus on listbox and moves cursor', - exampleFile, 'listbox-key-home', async (t) => { - + } +); + +ariaTest( + 'Home from focus on list puts focus on listbox and moves cursor', + exampleFile, + 'listbox-key-home', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "ARROW_HOME" @@ -534,15 +713,20 @@ ariaTest('Home from focus on list puts focus on listbox and moves cursor', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after one ARROW_HOME key', + 'Focus should be on the textbox after one ARROW_HOME key' ); - }); - -ariaTest('End from focus on list puts focus on listbox', - exampleFile, 'listbox-key-end', async (t) => { - + } +); + +ariaTest( + 'End from focus on list puts focus on listbox', + exampleFile, + 'listbox-key-end', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('a', Key.ARROW_DOWN); // Send key "END_ARROW" @@ -556,16 +740,20 @@ ariaTest('End from focus on list puts focus on listbox', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after on ARROW_END key', + 'Focus should be on the textbox after on ARROW_END key' ); - }); - - -ariaTest('Sending character keys while focus is on listbox moves focus', - exampleFile, 'listbox-key-char', async (t) => { - + } +); + +ariaTest( + 'Sending character keys while focus is on listbox moves focus', + exampleFile, + 'listbox-key-char', + async (t) => { // Send key "ARROW_DOWN" to put the focus on the listbox - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys(Key.ARROW_DOWN); // Send key "a" @@ -581,18 +769,23 @@ ariaTest('Sending character keys while focus is on listbox moves focus', t.is( await textbox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the textbox after sending a character key while the focus is on the listbox', + 'Focus should be on the textbox after sending a character key while the focus is on the listbox' ); + } +); - }); - -ariaTest('Expected behavior for all other standard single line editing keys', - exampleFile, 'standard-single-line-editing-keys', async (t) => { - - let numOptions = (await t.context.queryElements(t, ex.optionsSelector)).length; +ariaTest( + 'Expected behavior for all other standard single line editing keys', + exampleFile, + 'standard-single-line-editing-keys', + async (t) => { + let numOptions = (await t.context.queryElements(t, ex.optionsSelector)) + .length; // Send key "w" - const textbox = await t.context.session.findElement(By.css(ex.textboxSelector)); + const textbox = await t.context.session.findElement( + By.css(ex.textboxSelector) + ); await textbox.sendKeys('w'); t.is( @@ -600,4 +793,5 @@ ariaTest('Expected behavior for all other standard single line editing keys', numOptions, 'Sending standard editing keys should NOT filter results' ); - }); + } +); diff --git a/test/tests/combobox_datepicker.js b/test/tests/combobox_datepicker.js index 08380243b6..e9b4910c6d 100644 --- a/test/tests/combobox_datepicker.js +++ b/test/tests/combobox_datepicker.js @@ -35,7 +35,7 @@ const ex = { prevYear: '#ex1 [role="dialog"] button.prev-year', prevMonth: '#ex1 [role="dialog"] button.prev-month', nextMonth: '#ex1 [role="dialog"] button.next-month', - nextYear: '#ex1 [role="dialog"] button.next-year' + nextYear: '#ex1 [role="dialog"] button.next-year', }; ex.allFocusableElementsInDialog = [ @@ -45,25 +45,29 @@ ex.allFocusableElementsInDialog = [ ex.prevYear, ex.prevMonth, ex.nextMonth, - ex.nextYear -] + ex.nextYear, +]; const clickFirstOfMonth = async function (t) { let today = new Date(); - today.setUTCHours(0,0,0,0); + today.setUTCHours(0, 0, 0, 0); let firstOfMonth = new Date(today); firstOfMonth.setDate(1); let firstOfMonthString = today.toISOString().split('T')[0]; - return (await t.context.queryElement(t, `[data-date=${firstOfMonthString}]`)).click(); + return ( + await t.context.queryElement(t, `[data-date=${firstOfMonthString}]`) + ).click(); }; const clickToday = async function (t) { let today = new Date(); - today.setUTCHours(0,0,0,0); + today.setUTCHours(0, 0, 0, 0); let todayString = today.toISOString().split('T')[0]; - return (await t.context.queryElement(t, `[data-date=${todayString}]`)).click(); + return ( + await t.context.queryElement(t, `[data-date=${todayString}]`) + ).click(); }; const setDateToJanFirst2019 = async function (t) { @@ -89,334 +93,590 @@ ariaTest('Combobox: has role', exampleFile, 'textbox-role', async (t) => { await assertAriaRoles(t, 'ex1', 'combobox', 1, 'input'); }); -ariaTest('Combobox: has aria-haspopup set to "dialog"', exampleFile, 'textbox-aria-haspopup', async (t) => { - await assertAttributeValues(t, ex.comboboxSelector, 'aria-haspopup', 'dialog'); -}); - -ariaTest('Combobox: has aria-contorls set to "id-dialog-1"', exampleFile, 'textbox-aria-controls', async (t) => { - await assertAttributeValues(t, ex.comboboxSelector, 'aria-controls', 'cb-dialog-1'); -}); - -ariaTest('Combobox: has aria-contorls set to "id-descrption-1"', exampleFile, 'textbox-aria-describedby', async (t) => { - await assertAttributeValues(t, ex.comboboxSelector, 'aria-describedby', 'cb-description-1'); -}); - - -ariaTest('Combobox: Initially aria-expanded set to "false"', exampleFile, 'textbox-aria-expanded-false', async (t) => { - await assertAttributeValues(t, ex.comboboxSelector, 'aria-expanded', 'false'); -}); - -ariaTest('Combobox: aria-expanded set to "true" when dialog is open', exampleFile, 'textbox-aria-expanded-true', async (t) => { - // Open dialog box - await (await t.context.queryElement(t, ex.comboboxSelector)).sendKeys(Key.ARROW_DOWN); - await assertAttributeValues(t, ex.comboboxSelector, 'aria-expanded', 'true'); -}); - - +ariaTest( + 'Combobox: has aria-haspopup set to "dialog"', + exampleFile, + 'textbox-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-haspopup', + 'dialog' + ); + } +); + +ariaTest( + 'Combobox: has aria-contorls set to "id-dialog-1"', + exampleFile, + 'textbox-aria-controls', + async (t) => { + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-controls', + 'cb-dialog-1' + ); + } +); + +ariaTest( + 'Combobox: has aria-contorls set to "id-descrption-1"', + exampleFile, + 'textbox-aria-describedby', + async (t) => { + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-describedby', + 'cb-description-1' + ); + } +); + +ariaTest( + 'Combobox: Initially aria-expanded set to "false"', + exampleFile, + 'textbox-aria-expanded-false', + async (t) => { + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-expanded', + 'false' + ); + } +); + +ariaTest( + 'Combobox: aria-expanded set to "true" when dialog is open', + exampleFile, + 'textbox-aria-expanded-true', + async (t) => { + // Open dialog box + await (await t.context.queryElement(t, ex.comboboxSelector)).sendKeys( + Key.ARROW_DOWN + ); + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-expanded', + 'true' + ); + } +); // Button Tests -ariaTest('Button: "aria-label" attribute', exampleFile, 'calendar-button-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.buttonSelector); -}); - -ariaTest('Button: "tabindex" is set to -1', exampleFile, 'calendar-button-tabindex', async (t) => { - await assertAttributeValues(t, ex.buttonSelector, 'tabindex', '-1'); -}); - +ariaTest( + 'Button: "aria-label" attribute', + exampleFile, + 'calendar-button-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.buttonSelector); + } +); + +ariaTest( + 'Button: "tabindex" is set to -1', + exampleFile, + 'calendar-button-tabindex', + async (t) => { + await assertAttributeValues(t, ex.buttonSelector, 'tabindex', '-1'); + } +); // Dialog Tests -ariaTest('role="dialog" attribute on div', exampleFile, 'dialog-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'dialog', 1, 'div'); -}); - -ariaTest('aria-modal="true" on modal', exampleFile, 'dialog-aria-modal', async (t) => { - await assertAttributeValues(t, ex.dialogSelector, 'aria-modal', 'true'); -}); - -ariaTest('aria-label exist on dialog', exampleFile, 'dialog-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.dialogSelector); -}); - -ariaTest('aria-live="polite" on keyboard support message', exampleFile, 'dialog-aria-live', async (t) => { - await assertAttributeValues(t, ex.dialogMessageSelector, 'aria-live', 'polite'); -}); - -ariaTest('"aria-label" exists on control buttons', exampleFile, 'calendar-navigation-button-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.calendarNavigationButtonSelector); -}); - -ariaTest('aria-live="polite" on dialog header', exampleFile, 'calendar-navigation-aria-live', async (t) => { - await assertAttributeValues(t, `${ex.dialogSelector} h2`, 'aria-live', 'polite'); -}); +ariaTest( + 'role="dialog" attribute on div', + exampleFile, + 'dialog-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'dialog', 1, 'div'); + } +); + +ariaTest( + 'aria-modal="true" on modal', + exampleFile, + 'dialog-aria-modal', + async (t) => { + await assertAttributeValues(t, ex.dialogSelector, 'aria-modal', 'true'); + } +); + +ariaTest( + 'aria-label exist on dialog', + exampleFile, + 'dialog-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.dialogSelector); + } +); + +ariaTest( + 'aria-live="polite" on keyboard support message', + exampleFile, + 'dialog-aria-live', + async (t) => { + await assertAttributeValues( + t, + ex.dialogMessageSelector, + 'aria-live', + 'polite' + ); + } +); + +ariaTest( + '"aria-label" exists on control buttons', + exampleFile, + 'calendar-navigation-button-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.calendarNavigationButtonSelector); + } +); + +ariaTest( + 'aria-live="polite" on dialog header', + exampleFile, + 'calendar-navigation-aria-live', + async (t) => { + await assertAttributeValues( + t, + `${ex.dialogSelector} h2`, + 'aria-live', + 'polite' + ); + } +); ariaTest('grid role on table element', exampleFile, 'grid-role', async (t) => { await assertAriaRoles(t, 'ex1', 'grid', 1, 'table'); }); -ariaTest('aria-labelledby on grid element', exampleFile, 'grid-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.gridSelector); -}); - - -ariaTest('Roving tab index on dates in gridcell', exampleFile, 'gridcell-tabindex', async (t) => { - let button = await t.context.queryElement(t, ex.buttonSelector); - await setDateToJanFirst2019(t); - - await button.sendKeys(Key.ENTER); - - let focusableButtons = await t.context.queryElements(t, ex.currentMonthDateButtons); - let allButtons = await t.context.queryElements(t, ex.allDates); +ariaTest( + 'aria-labelledby on grid element', + exampleFile, + 'grid-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.gridSelector); + } +); - // test only one element has tabindex="0" - for (let tabableEl = 0; tabableEl < focusableButtons.length; tabableEl++) { - let dateSelected = await focusableButtons[tabableEl].getText(); +ariaTest( + 'Roving tab index on dates in gridcell', + exampleFile, + 'gridcell-tabindex', + async (t) => { + let button = await t.context.queryElement(t, ex.buttonSelector); + await setDateToJanFirst2019(t); - for (let el = 0; el < allButtons.length; el++) { - let date = await allButtons[el].getText(); - let disabled = (await allButtons[el].getAttribute('class')).includes('disabled'); - let tabindex = dateSelected === date && !disabled ? '0' : '-1'; - t.log('Tabindex: ' + tabindex + ' DS: ' + dateSelected + ' D: ' + date + ' Disabled: ' + disabled); + await button.sendKeys(Key.ENTER); - t.is( - await allButtons[el].getAttribute('tabindex'), - tabindex, - 'focus is on day ' + (tabableEl + 1) + ' therefore the button number ' + - el + ' should have tab index set to: ' + tabindex - ); + let focusableButtons = await t.context.queryElements( + t, + ex.currentMonthDateButtons + ); + let allButtons = await t.context.queryElements(t, ex.allDates); + + // test only one element has tabindex="0" + for (let tabableEl = 0; tabableEl < focusableButtons.length; tabableEl++) { + let dateSelected = await focusableButtons[tabableEl].getText(); + + for (let el = 0; el < allButtons.length; el++) { + let date = await allButtons[el].getText(); + let disabled = (await allButtons[el].getAttribute('class')).includes( + 'disabled' + ); + let tabindex = dateSelected === date && !disabled ? '0' : '-1'; + t.log( + 'Tabindex: ' + + tabindex + + ' DS: ' + + dateSelected + + ' D: ' + + date + + ' Disabled: ' + + disabled + ); + + t.is( + await allButtons[el].getAttribute('tabindex'), + tabindex, + 'focus is on day ' + + (tabableEl + 1) + + ' therefore the button number ' + + el + + ' should have tab index set to: ' + + tabindex + ); + } + + // Send the tabindex="0" element the appropriate key to switch focus to the next element + await focusableButtons[tabableEl].sendKeys(Key.ARROW_RIGHT); } - - // Send the tabindex="0" element the appropriate key to switch focus to the next element - await focusableButtons[tabableEl].sendKeys(Key.ARROW_RIGHT); } -}); - -ariaTest('aria-selected on selected date', exampleFile, 'gridcell-aria-selected', async (t) => { - let button = await t.context.queryElement(t, ex.buttonSelector); - - await button.click(); - await assertAttributeDNE(t, ex.allDates, 'aria-selected'); +); - await setDateToJanFirst2019(t); - await button.click(); - await assertAttributeValues(t, ex.jan12019Day, 'aria-selected', 'true'); +ariaTest( + 'aria-selected on selected date', + exampleFile, + 'gridcell-aria-selected', + async (t) => { + let button = await t.context.queryElement(t, ex.buttonSelector); - let selectedButtons = await t.context.queryElements(t, `${ex.allDates}[aria-selected="true"]`); + await button.click(); + await assertAttributeDNE(t, ex.allDates, 'aria-selected'); - t.is( - selectedButtons.length, - 1, - 'after setting date in box, only one button should have aria-selected' - ); + await setDateToJanFirst2019(t); + await button.click(); + await assertAttributeValues(t, ex.jan12019Day, 'aria-selected', 'true'); - await (await t.context.queryElement(t, ex.jan22019Day)).click(); - await button.click(); - await assertAttributeValues(t, ex.jan22019Day, 'aria-selected', 'true'); - - selectedButtons = await t.context.queryElements(t, `${ex.allDates}[aria-selected="true"]`); - - t.is( - selectedButtons.length, - 1, - 'after clicking a date and re-opening datepicker, only one button should have aria-selected' - ); - -}); - -// Keyboard - - -ariaTest('DOWN ARROW, ALT plus DOWN ARROW and ENTER to open datepicker', exampleFile, 'combobox-down-arrow', async (t) => { - let combobox = await t.context.queryElement(t, ex.comboboxSelector); - let dialog = await t.context.queryElement(t, ex.dialogSelector); - let cancel = await t.context.queryElement(t, ex.cancelSelector); - - // Test DOWN ARROW key - await combobox.sendKeys(Key.ARROW_DOWN); - - t.not( - await dialog.getCssValue('display'), - 'none', - 'After sending DOWN ARROW to the combobox, the calendar dialog should open' - ); - - // Close dialog - await cancel.sendKeys(Key.ENTER); - - t.not( - await dialog.getCssValue('display'), - 'block', - 'After sending ESCAPE to the dialog, the calendar dialog should close' - ); - - // Test ALT + DOWN ARROW key - await combobox.sendKeys(Key.ALT, Key.ARROW_DOWN); - - t.not( - await dialog.getCssValue('display'), - 'none', - 'After sending DOWN ARROW to the combobox, the calendar dialog should open' - ); - - // Close dialog - await cancel.sendKeys(Key.ENTER); - - t.not( - await dialog.getCssValue('display'), - 'block', - 'After sending ESCAPE to the dialog, the calendar dialog should close' - ); - -}); - -ariaTest('Sending key ESC when focus is in dialog closes dialog', exampleFile, 'dialog-esc', async (t) => { - let chooseDateButton = await t.context.queryElement(t, ex.buttonSelector); + let selectedButtons = await t.context.queryElements( + t, + `${ex.allDates}[aria-selected="true"]` + ); - for (let i = 0; i < ex.allFocusableElementsInDialog.length; i++) { + t.is( + selectedButtons.length, + 1, + 'after setting date in box, only one button should have aria-selected' + ); - await chooseDateButton.sendKeys(Key.ENTER); - let el = await t.context.queryElement(t, ex.allFocusableElementsInDialog[i]); - await el.sendKeys(Key.ESCAPE); + await (await t.context.queryElement(t, ex.jan22019Day)).click(); + await button.click(); + await assertAttributeValues(t, ex.jan22019Day, 'aria-selected', 'true'); - t.is( - await (await t.context.queryElement(t, ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending ESC to element "' + ex.allFocusableElementsInDialog[i] + '" in the dialog, the calendar dialog should close' + selectedButtons = await t.context.queryElements( + t, + `${ex.allDates}[aria-selected="true"]` ); t.is( - await (await t.context.queryElement(t, ex.comboboxSelector)).getAttribute('value'), - '', - 'After sending ESC to element "' + ex.allFocusableElementsInDialog[i] + '" in the dialog, no date should be selected' + selectedButtons.length, + 1, + 'after clicking a date and re-opening datepicker, only one button should have aria-selected' ); } -}); +); -ariaTest('ENTER on previous year or month and SPACE on next year or month changes the year or month', exampleFile, 'month-year-button-space-return', async (t) => { - await (await t.context.queryElement(t, ex.buttonSelector)).sendKeys(Key.ENTER); - - let monthYear = await t.context.queryElement(t, ex.monthYear); - let originalMonthYear = await monthYear.getText(); +// Keyboard - for (let yearOrMonth of ['Year', 'Month']) { - let yearOrMonthLower = yearOrMonth.toLowerCase(); +ariaTest( + 'DOWN ARROW, ALT plus DOWN ARROW and ENTER to open datepicker', + exampleFile, + 'combobox-down-arrow', + async (t) => { + let combobox = await t.context.queryElement(t, ex.comboboxSelector); + let dialog = await t.context.queryElement(t, ex.dialogSelector); + let cancel = await t.context.queryElement(t, ex.cancelSelector); - // enter on previous year or month should change the monthYear text - await (await t.context.queryElement(t, ex[`prev${yearOrMonth}`])).sendKeys(Key.ENTER); + // Test DOWN ARROW key + await combobox.sendKeys(Key.ARROW_DOWN); t.not( - await monthYear.getText(), - originalMonthYear, - `After sending ENTER on the "previous ${yearOrMonthLower}" button, the month and year text should be not be ${originalMonthYear}` + await dialog.getCssValue('display'), + 'none', + 'After sending DOWN ARROW to the combobox, the calendar dialog should open' ); - // space on next year or month should change it back to the original - await (await t.context.queryElement(t, ex[`next${yearOrMonth}`])).sendKeys(' '); + // Close dialog + await cancel.sendKeys(Key.ENTER); - t.is( - await monthYear.getText(), - originalMonthYear, - `After sending SPACE on the "next ${yearOrMonthLower}" button, the month and year text should be ${originalMonthYear}` + t.not( + await dialog.getCssValue('display'), + 'block', + 'After sending ESCAPE to the dialog, the calendar dialog should close' ); - } -}); -ariaTest('Tab should go through all tabbable items, then repear', exampleFile, 'dialog-tab', async (t) => { - await (await t.context.queryElement(t, ex.buttonSelector)).sendKeys(Key.ENTER); + // Test ALT + DOWN ARROW key + await combobox.sendKeys(Key.ALT, Key.ARROW_DOWN); - for (let itemSelector of ex.allFocusableElementsInDialog) { - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should be on: ' + itemSelector + t.not( + await dialog.getCssValue('display'), + 'none', + 'After sending DOWN ARROW to the combobox, the calendar dialog should open' ); - await (await t.context.queryElement(t, itemSelector)).sendKeys(Key.TAB); - } - - t.true( - await focusMatchesElement(t, ex.allFocusableElementsInDialog[0]), - 'After tabbing through all items, focus should return to: ' + ex.allFocusableElementsInDialog[0] - ); -}); - -ariaTest('Shift-tab should move focus backwards', exampleFile, 'dialog-shift-tab', async (t) => { - t.plan(7); - - await (await t.context.queryElement(t, ex.buttonSelector)).sendKeys(Key.ENTER); + // Close dialog + await cancel.sendKeys(Key.ENTER); - await (await t.context.queryElement(t, ex.allFocusableElementsInDialog[0])) - .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - - let lastIndex = ex.allFocusableElementsInDialog.length - 1; - for (let i = lastIndex; i >= 0; i--) { - t.true( - await focusMatchesElement(t, ex.allFocusableElementsInDialog[i]), - 'Focus should be on: ' + ex.allFocusableElementsInDialog[i] + t.not( + await dialog.getCssValue('display'), + 'block', + 'After sending ESCAPE to the dialog, the calendar dialog should close' ); + } +); + +ariaTest( + 'Sending key ESC when focus is in dialog closes dialog', + exampleFile, + 'dialog-esc', + async (t) => { + let chooseDateButton = await t.context.queryElement(t, ex.buttonSelector); + + for (let i = 0; i < ex.allFocusableElementsInDialog.length; i++) { + await chooseDateButton.sendKeys(Key.ENTER); + let el = await t.context.queryElement( + t, + ex.allFocusableElementsInDialog[i] + ); + await el.sendKeys(Key.ESCAPE); - await (await t.context.queryElement(t, ex.allFocusableElementsInDialog[i])) - .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + t.is( + await (await t.context.queryElement(t, ex.dialogSelector)).getCssValue( + 'display' + ), + 'none', + 'After sending ESC to element "' + + ex.allFocusableElementsInDialog[i] + + '" in the dialog, the calendar dialog should close' + ); + + t.is( + await ( + await t.context.queryElement(t, ex.comboboxSelector) + ).getAttribute('value'), + '', + 'After sending ESC to element "' + + ex.allFocusableElementsInDialog[i] + + '" in the dialog, no date should be selected' + ); + } } -}); +); + +ariaTest( + 'ENTER on previous year or month and SPACE on next year or month changes the year or month', + exampleFile, + 'month-year-button-space-return', + async (t) => { + await (await t.context.queryElement(t, ex.buttonSelector)).sendKeys( + Key.ENTER + ); -// TODO(zcorpan): Missing tests. Either mark as "test-not-required" or write the test. -ariaTest.failing(`Test not implemented: grid-space`, exampleFile, 'grid-space', async (t) => { - t.fail(); -}); + let monthYear = await t.context.queryElement(t, ex.monthYear); + let originalMonthYear = await monthYear.getText(); -ariaTest.failing(`Test not implemented: grid-return`, exampleFile, 'grid-return', async (t) => { - t.fail(); -}); + for (let yearOrMonth of ['Year', 'Month']) { + let yearOrMonthLower = yearOrMonth.toLowerCase(); -ariaTest.failing(`Test not implemented: grid-up-arrow`, exampleFile, 'grid-up-arrow', async (t) => { - t.fail(); -}); + // enter on previous year or month should change the monthYear text + await ( + await t.context.queryElement(t, ex[`prev${yearOrMonth}`]) + ).sendKeys(Key.ENTER); -ariaTest.failing(`Test not implemented: grid-down-arrow`, exampleFile, 'grid-down-arrow', async (t) => { - t.fail(); -}); + t.not( + await monthYear.getText(), + originalMonthYear, + `After sending ENTER on the "previous ${yearOrMonthLower}" button, the month and year text should be not be ${originalMonthYear}` + ); -ariaTest.failing(`Test not implemented: grid-right-arrow`, exampleFile, 'grid-right-arrow', async (t) => { - t.fail(); -}); + // space on next year or month should change it back to the original + await ( + await t.context.queryElement(t, ex[`next${yearOrMonth}`]) + ).sendKeys(' '); -ariaTest.failing(`Test not implemented: grid-left-arrow`, exampleFile, 'grid-left-arrow', async (t) => { - t.fail(); -}); + t.is( + await monthYear.getText(), + originalMonthYear, + `After sending SPACE on the "next ${yearOrMonthLower}" button, the month and year text should be ${originalMonthYear}` + ); + } + } +); + +ariaTest( + 'Tab should go through all tabbable items, then repear', + exampleFile, + 'dialog-tab', + async (t) => { + await (await t.context.queryElement(t, ex.buttonSelector)).sendKeys( + Key.ENTER + ); -ariaTest.failing(`Test not implemented: grid-home`, exampleFile, 'grid-home', async (t) => { - t.fail(); -}); + for (let itemSelector of ex.allFocusableElementsInDialog) { + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should be on: ' + itemSelector + ); -ariaTest.failing(`Test not implemented: grid-end`, exampleFile, 'grid-end', async (t) => { - t.fail(); -}); + await (await t.context.queryElement(t, itemSelector)).sendKeys(Key.TAB); + } -ariaTest.failing(`Test not implemented: grid-pageup`, exampleFile, 'grid-pageup', async (t) => { - t.fail(); -}); + t.true( + await focusMatchesElement(t, ex.allFocusableElementsInDialog[0]), + 'After tabbing through all items, focus should return to: ' + + ex.allFocusableElementsInDialog[0] + ); + } +); -ariaTest.failing(`Test not implemented: grid-shift-pageup`, exampleFile, 'grid-shift-pageup', async (t) => { - t.fail(); -}); +ariaTest( + 'Shift-tab should move focus backwards', + exampleFile, + 'dialog-shift-tab', + async (t) => { + t.plan(7); -ariaTest.failing(`Test not implemented: grid-pagedown`, exampleFile, 'grid-pagedown', async (t) => { - t.fail(); -}); + await (await t.context.queryElement(t, ex.buttonSelector)).sendKeys( + Key.ENTER + ); -ariaTest.failing(`Test not implemented: grid-shift-pagedown`, exampleFile, 'grid-shift-pagedown', async (t) => { - t.fail(); -}); + await ( + await t.context.queryElement(t, ex.allFocusableElementsInDialog[0]) + ).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); -ariaTest.failing(`Test not implemented: okay-cancel-button-space-return`, exampleFile, 'okay-cancel-button-space-return', async (t) => { - t.fail(); -}); + let lastIndex = ex.allFocusableElementsInDialog.length - 1; + for (let i = lastIndex; i >= 0; i--) { + t.true( + await focusMatchesElement(t, ex.allFocusableElementsInDialog[i]), + 'Focus should be on: ' + ex.allFocusableElementsInDialog[i] + ); -ariaTest.failing(`Test not implemented: textbox-aria-autocomplete`, exampleFile, 'textbox-aria-autocomplete', async (t) => { - t.fail(); -}); + await ( + await t.context.queryElement(t, ex.allFocusableElementsInDialog[i]) + ).sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + } + } +); -ariaTest.failing(`Test not implemented: textbox-aria-live`, exampleFile, 'textbox-aria-live', async (t) => { - t.fail(); -}); +// TODO(zcorpan): Missing tests. Either mark as "test-not-required" or write the test. +ariaTest.failing( + `Test not implemented: grid-space`, + exampleFile, + 'grid-space', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-return`, + exampleFile, + 'grid-return', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-up-arrow`, + exampleFile, + 'grid-up-arrow', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-down-arrow`, + exampleFile, + 'grid-down-arrow', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-right-arrow`, + exampleFile, + 'grid-right-arrow', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-left-arrow`, + exampleFile, + 'grid-left-arrow', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-home`, + exampleFile, + 'grid-home', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-end`, + exampleFile, + 'grid-end', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-pageup`, + exampleFile, + 'grid-pageup', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-shift-pageup`, + exampleFile, + 'grid-shift-pageup', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-pagedown`, + exampleFile, + 'grid-pagedown', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: grid-shift-pagedown`, + exampleFile, + 'grid-shift-pagedown', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: okay-cancel-button-space-return`, + exampleFile, + 'okay-cancel-button-space-return', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: textbox-aria-autocomplete`, + exampleFile, + 'textbox-aria-autocomplete', + async (t) => { + t.fail(); + } +); + +ariaTest.failing( + `Test not implemented: textbox-aria-live`, + exampleFile, + 'textbox-aria-live', + async (t) => { + t.fail(); + } +); diff --git a/test/tests/combobox_grid-combo.js b/test/tests/combobox_grid-combo.js index 9b35dbcc5b..6f5755b2e0 100644 --- a/test/tests/combobox_grid-combo.js +++ b/test/tests/combobox_grid-combo.js @@ -16,7 +16,7 @@ const ex = { rowSelector: '#ex1 [role="row"]', gridcellSelector: '#ex1 [role="gridcell"]', gridcellFocusedClass: 'focused-cell', - numAOptions: 3 + numAOptions: 3, }; const waitForFocusChange = async (t, comboboxSelector, originalFocus) => { @@ -28,16 +28,22 @@ const waitForFocusChange = async (t, comboboxSelector, originalFocus) => { return newfocus != originalFocus; }, t.context.waitTime, - 'Timeout waiting for "aria-activedescendant" value to change from "' + originalFocus + '". ' + 'Timeout waiting for "aria-activedescendant" value to change from "' + + originalFocus + + '". ' ); }; const confirmCursorIndex = async (t, selector, cursorIndex) => { - return t.context.session.executeScript(function () { - const [selector, cursorIndex] = arguments; - let item = document.querySelector(selector); - return item.selectionStart === cursorIndex; - }, selector, cursorIndex); + return t.context.session.executeScript( + function () { + const [selector, cursorIndex] = arguments; + let item = document.querySelector(selector); + return item.selectionStart === cursorIndex; + }, + selector, + cursorIndex + ); }; const gridcellId = (row, column) => { @@ -45,163 +51,258 @@ const gridcellId = (row, column) => { }; // Attributes -ariaTest('Test for role="combobox"', exampleFile, 'combobox-role', async (t) => { +ariaTest( + 'Test for role="combobox"', + exampleFile, + 'combobox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'combobox', '1', 'input'); -}); - -ariaTest('"aria-haspopup"=grid on combobox element', exampleFile, 'combobox-aria-haspopup', async (t) => { - await assertAttributeValues(t, ex.comboboxSelector, 'aria-haspopup', 'grid'); -}); + } +); + +ariaTest( + '"aria-haspopup"=grid on combobox element', + exampleFile, + 'combobox-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-haspopup', + 'grid' + ); + } +); + +ariaTest( + '"aria-expanded" on combobox element', + exampleFile, + 'combobox-aria-expanded', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboboxSelector) + ); + + // Check that aria-expanded is false and the grid is not visible before interacting -ariaTest('"aria-expanded" on combobox element', exampleFile, 'combobox-aria-expanded', async (t) => { - - const combobox = await t.context.session.findElement(By.css(ex.comboboxSelector)); - - // Check that aria-expanded is false and the grid is not visible before interacting - - t.is( - await combobox.getAttribute('aria-expanded'), - 'false', - 'combobox element should have attribute "aria-expanded" set to false by default.' - ); - - const popupId = await t.context.session - .findElement(By.css(ex.comboboxSelector)) - .getAttribute('aria-controls'); - - const popupElement = await t.context.session - .findElement(By.id('ex1')) - .findElement(By.id(popupId)); - - t.false( - await popupElement.isDisplayed(), - 'Popup element should not be displayed when \'aria-expanded\' is false\'' - ); - - // Send key "a" to combobox - - await t.context.session - .findElement(By.css(ex.comboboxSelector)) - .sendKeys('a'); + t.is( + await combobox.getAttribute('aria-expanded'), + 'false', + 'combobox element should have attribute "aria-expanded" set to false by default.' + ); - // Check that aria-expanded is true and the grid is visible + const popupId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .getAttribute('aria-controls'); - t.is( - await combobox.getAttribute('aria-expanded'), - 'true', - 'combobox element should have attribute "aria-expand" set to true after typing.' - ); + const popupElement = await t.context.session + .findElement(By.id('ex1')) + .findElement(By.id(popupId)); - t.true( - await popupElement.isDisplayed(), - 'Popup element should be displayed when \'aria-expanded\' is true\'' - ); -}); + t.false( + await popupElement.isDisplayed(), + "Popup element should not be displayed when 'aria-expanded' is false'" + ); -ariaTest('"id" attribute on textbox used to discover accessible name', exampleFile, 'combobox-id', async (t) => { + // Send key "a" to combobox - const labelForTextboxId = await t.context.session - .findElement(By.css(ex.labelSelector)) - .getAttribute('for'); + await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .sendKeys('a'); - t.truthy( - labelForTextboxId, - '"for" attribute with id value should exist on label: ' + ex.labelSelector - ); + // Check that aria-expanded is true and the grid is visible - const textboxElementId = await t.context.session - .findElement(By.css(ex.comboboxSelector)) - .getAttribute('id'); + t.is( + await combobox.getAttribute('aria-expanded'), + 'true', + 'combobox element should have attribute "aria-expand" set to true after typing.' + ); - t.true( - labelForTextboxId === textboxElementId, - 'Id on textbox element (' + ex.comboboxSelector + ') should match the "for" attribute of the label (' + labelForTextboxId + ')' - ); -}); + t.true( + await popupElement.isDisplayed(), + "Popup element should be displayed when 'aria-expanded' is true'" + ); + } +); -ariaTest('"aria-autocomplete" on grid element', exampleFile, 'combobox-aria-autocomplete', async (t) => { - await assertAttributeValues(t, ex.comboboxSelector, 'aria-autocomplete', 'list'); -}); +ariaTest( + '"id" attribute on textbox used to discover accessible name', + exampleFile, + 'combobox-id', + async (t) => { + const labelForTextboxId = await t.context.session + .findElement(By.css(ex.labelSelector)) + .getAttribute('for'); -ariaTest('"aria-controls" attribute on grid element', exampleFile, 'combobox-aria-controls', async (t) => { + t.truthy( + labelForTextboxId, + '"for" attribute with id value should exist on label: ' + ex.labelSelector + ); - const popupId = await t.context.session - .findElement(By.css(ex.comboboxSelector)) - .getAttribute('aria-controls'); + const textboxElementId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .getAttribute('id'); - t.truthy( - popupId, - '"aria-controls" attribute should exist on: ' + ex.comboboxSelector - ); + t.true( + labelForTextboxId === textboxElementId, + 'Id on textbox element (' + + ex.comboboxSelector + + ') should match the "for" attribute of the label (' + + labelForTextboxId + + ')' + ); + } +); + +ariaTest( + '"aria-autocomplete" on grid element', + exampleFile, + 'combobox-aria-autocomplete', + async (t) => { + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-autocomplete', + 'list' + ); + } +); + +ariaTest( + '"aria-controls" attribute on grid element', + exampleFile, + 'combobox-aria-controls', + async (t) => { + const popupId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .getAttribute('aria-controls'); - const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); + t.truthy( + popupId, + '"aria-controls" attribute should exist on: ' + ex.comboboxSelector + ); - t.is( - popupElements.length, - 1, - 'There should be a element with id "' + popupId + '" as referenced by the aria-controls attribute' - ); -}); + const popupElements = await t.context.queryElements(t, `#ex1 #${popupId}`); -ariaTest('"aria-activedescendant" on combobox element', exampleFile, 'combobox-aria-activedescendant', async (t) => { - await assertAttributeValues(t, ex.comboboxSelector, 'aria-activedescendant', null); -}); + t.is( + popupElements.length, + 1, + 'There should be a element with id "' + + popupId + + '" as referenced by the aria-controls attribute' + ); + } +); + +ariaTest( + '"aria-activedescendant" on combobox element', + exampleFile, + 'combobox-aria-activedescendant', + async (t) => { + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-activedescendant', + null + ); + } +); ariaTest('role "grid" on div element', exampleFile, 'grid-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'grid', '1', 'div'); + await assertAriaRoles(t, 'ex1', 'grid', '1', 'div'); }); -ariaTest('"aria-labelledby" attribute on grid element', exampleFile, 'grid-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" attribute on grid element', + exampleFile, + 'grid-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.gridSelector); -}); - -ariaTest('role "row" exists within grid element', exampleFile, 'row-role', async (t) => { - - // Send key "a" then arrow down to reveal all options - await t.context.session.findElement(By.css(ex.comboboxSelector)).sendKeys('a', Key.ARROW_DOWN); + } +); + +ariaTest( + 'role "row" exists within grid element', + exampleFile, + 'row-role', + async (t) => { + // Send key "a" then arrow down to reveal all options + await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .sendKeys('a', Key.ARROW_DOWN); - let gridEl = await t.context.session.findElement(By.css(ex.gridSelector)); - let rowElements = await t.context.queryElements(t, '[role="row"]', gridEl); + let gridEl = await t.context.session.findElement(By.css(ex.gridSelector)); + let rowElements = await t.context.queryElements(t, '[role="row"]', gridEl); - t.truthy( - await rowElements.length, - 'role="row" elements should be found within a gridcell element after opening popup' - ); -}); + t.truthy( + await rowElements.length, + 'role="row" elements should be found within a gridcell element after opening popup' + ); + } +); // This test fails due to bug: https://github.com/w3c/aria-practices/issues/859 -ariaTest.failing('"aria-selected" attribute on row element', exampleFile, 'row-aria-selected', async (t) => { - - // Send key "a" - await t.context.session.findElement(By.css(ex.comboboxSelector)).sendKeys('a'); - await assertAttributeDNE(t, ex.rowSelector + ':nth-of-type(1)', 'aria-selected'); - - // Send key ARROW_DOWN to selected first option - await t.context.session.findElement(By.css(ex.comboboxSelector)).sendKeys(Key.ARROW_DOWN); - await assertAttributeValues(t, ex.rowSelector + ':nth-of-type(1)', 'aria-selected', 'true'); -}); - -ariaTest('role "gridcell" exists within row element', exampleFile, 'gridcell-role', async (t) => { - - // Send key "a" then arrow down to reveal all options - await t.context.session.findElement(By.css(ex.comboboxSelector)).sendKeys('a', Key.ARROW_DOWN); +ariaTest.failing( + '"aria-selected" attribute on row element', + exampleFile, + 'row-aria-selected', + async (t) => { + // Send key "a" + await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .sendKeys('a'); + await assertAttributeDNE( + t, + ex.rowSelector + ':nth-of-type(1)', + 'aria-selected' + ); - let rowElement = await t.context.session.findElement(By.css(ex.rowSelector)); - let cellElements = await t.context.queryElements(t, '[role="gridcell"]', rowElement); + // Send key ARROW_DOWN to selected first option + await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .sendKeys(Key.ARROW_DOWN); + await assertAttributeValues( + t, + ex.rowSelector + ':nth-of-type(1)', + 'aria-selected', + 'true' + ); + } +); + +ariaTest( + 'role "gridcell" exists within row element', + exampleFile, + 'gridcell-role', + async (t) => { + // Send key "a" then arrow down to reveal all options + await t.context.session + .findElement(By.css(ex.comboboxSelector)) + .sendKeys('a', Key.ARROW_DOWN); - t.truthy( - await cellElements.length, - 'role="gridcell" elements should be found within a row element after opening popup' - ); -}); + let rowElement = await t.context.session.findElement( + By.css(ex.rowSelector) + ); + let cellElements = await t.context.queryElements( + t, + '[role="gridcell"]', + rowElement + ); + t.truthy( + await cellElements.length, + 'role="gridcell" elements should be found within a row element after opening popup' + ); + } +); // Keys -ariaTest('Test down key press with focus on combobox', - exampleFile, 'popup-key-down-arrow', async (t) => { - - +ariaTest( + 'Test down key press with focus on combobox', + exampleFile, + 'popup-key-down-arrow', + async (t) => { // Send ARROW_DOWN to the combobox await t.context.session .findElement(By.css(ex.comboboxSelector)) @@ -209,7 +310,9 @@ ariaTest('Test down key press with focus on combobox', // Check that the grid is displayed t.false( - await t.context.session.findElement(By.css(ex.gridSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.gridSelector)) + .isDisplayed(), 'In example ex3 grid should not be display after ARROW_DOWN keypress while combobox is empty' ); @@ -223,41 +326,47 @@ ariaTest('Test down key press with focus on combobox', // Check that the grid is displayed t.true( - await t.context.session.findElement(By.css(ex.gridSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.gridSelector)) + .isDisplayed(), 'In example ex3 grid should display after ARROW_DOWN keypress when combobox has character values' ); // Check that the active descendent focus is correct - let focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + let focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(0,0), - 'After down arrow sent to combobox, aria-activedescendant should be set to the first gridcell element: ' + gridcellId(0,0) + gridcellId(0, 0), + 'After down arrow sent to combobox, aria-activedescendant should be set to the first gridcell element: ' + + gridcellId(0, 0) ); - let focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(0,0))) + let focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(0, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(0,0) + '" should have visual focus' + 'Gridcell with id "' + gridcellId(0, 0) + '" should have visual focus' ); + } +); - }); - -ariaTest('Test down key press with focus on list', - exampleFile, 'popup-key-down-arrow', async (t) => { - - +ariaTest( + 'Test down key press with focus on list', + exampleFile, + 'popup-key-down-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_DOWN to combobox to set focus on grid await t.context.session .findElement(By.css(ex.comboboxSelector)) .sendKeys('a', Key.ARROW_DOWN); // Test that ARROW_DOWN moves active descendant focus on item in grid - for (let i = 1; i < ex.numAOptions; i++) { + for (let i = 1; i < ex.numAOptions; i++) { let oldfocus = await t.context.session .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); @@ -271,19 +380,22 @@ ariaTest('Test down key press with focus on list', // Check that the active descendent focus is correct - let focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + let focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(i,0), - 'After down up sent to combobox, aria-activedescendant should be set to this gridcell element: ' + gridcellId(i,0) + gridcellId(i, 0), + 'After down up sent to combobox, aria-activedescendant should be set to this gridcell element: ' + + gridcellId(i, 0) ); - let focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(i,0))) + let focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(i, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(i,0) + '" should have visual focus' + 'Gridcell with id "' + gridcellId(i, 0) + '" should have visual focus' ); } @@ -300,28 +412,31 @@ ariaTest('Test down key press with focus on list', // Focus should be on the first item - let focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + let focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(0,0), - 'After down arrow sent to last grid row, aria-activedescendant should be set to the first gridcell element: ' + gridcellId(0,0) + gridcellId(0, 0), + 'After down arrow sent to last grid row, aria-activedescendant should be set to the first gridcell element: ' + + gridcellId(0, 0) ); - let focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(0,0))) + let focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(0, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(0,0) + '" should have visual focus' + 'Gridcell with id "' + gridcellId(0, 0) + '" should have visual focus' ); + } +); - }); - - -ariaTest('Test up key press with focus on combobox', - exampleFile, 'popup-key-up-arrow', async (t) => { - - +ariaTest( + 'Test up key press with focus on combobox', + exampleFile, + 'popup-key-up-arrow', + async (t) => { // Send ARROW_UP to the combobox await t.context.session .findElement(By.css(ex.comboboxSelector)) @@ -329,7 +444,9 @@ ariaTest('Test up key press with focus on combobox', // Check that the grid is displayed t.false( - await t.context.session.findElement(By.css(ex.gridSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.gridSelector)) + .isDisplayed(), 'In example ex3 grid should not be display after ARROW_UP keypress while combobox is empty' ); @@ -343,34 +460,42 @@ ariaTest('Test up key press with focus on combobox', // Check that the grid is displayed t.true( - await t.context.session.findElement(By.css(ex.gridSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.gridSelector)) + .isDisplayed(), 'In example ex3 grid should display after ARROW_UP keypress when combobox has character values' ); // Check that the active descendent focus is correct - let focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + let focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(ex.numAOptions - 1,0), - 'After up arrow sent to combobox, aria-activedescendant should be set to the last row first gridcell element: ' + gridcellId(ex.numAOptions - 1,0) + gridcellId(ex.numAOptions - 1, 0), + 'After up arrow sent to combobox, aria-activedescendant should be set to the last row first gridcell element: ' + + gridcellId(ex.numAOptions - 1, 0) ); - let focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(ex.numAOptions - 1,0))) + let focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(ex.numAOptions - 1, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(ex.numAOptions - 1,0) + '" should have visual focus' - ); - - }); - -ariaTest('Test up key press with focus on grid', - exampleFile, 'popup-key-up-arrow', async (t) => { - - + 'Gridcell with id "' + + gridcellId(ex.numAOptions - 1, 0) + + '" should have visual focus' + ); + } +); + +ariaTest( + 'Test up key press with focus on grid', + exampleFile, + 'popup-key-up-arrow', + async (t) => { // Send 'a' to text box, then send ARROW_UP to combobox to put focus in combobox // Up arrow should move selection to the last item in the list await t.context.session @@ -378,7 +503,7 @@ ariaTest('Test up key press with focus on grid', .sendKeys('a', Key.ARROW_UP); // Test that ARROW_UP moves active descendant focus up one item in the grid - for (let index = ex.numAOptions - 2; index >= 0 ; index--) { + for (let index = ex.numAOptions - 2; index >= 0; index--) { let oldfocus = await t.context.session .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); @@ -392,19 +517,24 @@ ariaTest('Test up key press with focus on grid', // Check that the active descendent focus is correct - let focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + let focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(index,0), - 'After up arrow sent to combobox, aria-activedescendant should be set to gridcell element: ' + gridcellId(index,0) + gridcellId(index, 0), + 'After up arrow sent to combobox, aria-activedescendant should be set to gridcell element: ' + + gridcellId(index, 0) ); - let focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(index,0))) + let focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(index, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(index,0) + '" should have visual focus' + 'Gridcell with id "' + + gridcellId(index, 0) + + '" should have visual focus' ); } @@ -422,27 +552,33 @@ ariaTest('Test up key press with focus on grid', // Check that the active descendent focus is correct - let focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + let focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(ex.numAOptions - 1,0), - 'After up arrow sent to first element in popup, aria-activedescendant should be set to the last row first gridcell element: ' + gridcellId(ex.numAOptions - 1,0) + gridcellId(ex.numAOptions - 1, 0), + 'After up arrow sent to first element in popup, aria-activedescendant should be set to the last row first gridcell element: ' + + gridcellId(ex.numAOptions - 1, 0) ); - let focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(ex.numAOptions - 1,0))) + let focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(ex.numAOptions - 1, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(ex.numAOptions - 1,0) + '" should have visual focus' - ); - - }); - -ariaTest('Test enter key press with focus on grid', - exampleFile, 'popup-key-enter', async (t) => { - - + 'Gridcell with id "' + + gridcellId(ex.numAOptions - 1, 0) + + '" should have visual focus' + ); + } +); + +ariaTest( + 'Test enter key press with focus on grid', + exampleFile, + 'popup-key-enter', + async (t) => { // Send key "a" to the combobox, then key ARROW_DOWN to select the first item await t.context.session @@ -451,7 +587,9 @@ ariaTest('Test enter key press with focus on grid', // Get the value of the first option in the grid - const firstOption = await t.context.session.findElement(By.css(ex.gridcellSelector)).getText(); + const firstOption = await t.context.session + .findElement(By.css(ex.gridcellSelector)) + .getText(); // Send key ENTER @@ -461,7 +599,12 @@ ariaTest('Test enter key press with focus on grid', // Confirm that the grid is closed - await assertAttributeValues(t, ex.comboboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-expanded', + 'false' + ); // Confirm that the value of the combobox is now set to the first option @@ -472,11 +615,14 @@ ariaTest('Test enter key press with focus on grid', firstOption, 'key press "ENTER" should result in first option in combobox' ); + } +); - }); - -ariaTest('Test escape key press with focus on combobox', - exampleFile, 'textbox-key-escape', async (t) => { +ariaTest( + 'Test escape key press with focus on combobox', + exampleFile, + 'textbox-key-escape', + async (t) => { // Send key "a" then key "ARROW_DOWN to put the focus on the listbox, // then key ESCAPE to the textbox @@ -486,7 +632,12 @@ ariaTest('Test escape key press with focus on combobox', // Confirm the listbox is closed and the textbox is cleared - await assertAttributeValues(t, ex.comboboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.comboboxSelector)) @@ -494,12 +645,14 @@ ariaTest('Test escape key press with focus on combobox', 'a', 'In listbox key press "ESCAPE" should result in "a" in textbox' ); + } +); - }); - - -ariaTest('Test double escape key press with focus on combobox', - exampleFile, 'textbox-key-escape', async (t) => { +ariaTest( + 'Test double escape key press with focus on combobox', + exampleFile, + 'textbox-key-escape', + async (t) => { // Send key "a", then key ESCAPE twice to the textbox await t.context.session @@ -508,7 +661,12 @@ ariaTest('Test double escape key press with focus on combobox', // Confirm the listbox is closed and the textbox is cleared - await assertAttributeValues(t, ex.comboboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session .findElement(By.css(ex.comboboxSelector)) @@ -516,13 +674,14 @@ ariaTest('Test double escape key press with focus on combobox', '', 'In key press "ESCAPE" should result in clearing the textbox' ); + } +); - }); - - -ariaTest('Test escape key press with focus on popup', - exampleFile, 'popup-key-escape', async (t) => { - +ariaTest( + 'Test escape key press with focus on popup', + exampleFile, + 'popup-key-escape', + async (t) => { // Send key "a" then key "ARROW_DOWN to put the focus on the grid, // then key ESCAPE to the combobox @@ -539,14 +698,21 @@ ariaTest('Test escape key press with focus on popup', // Wait for gridbox to close await t.context.session.wait( async function () { - return !(await t.context.session.findElement(By.css(ex.gridSelector)).isDisplayed()); + return !(await t.context.session + .findElement(By.css(ex.gridSelector)) + .isDisplayed()); }, t.context.waitTime, 'Timeout waiting for gridbox to close afer escape' ); // Confirm the grid is closed and the textbox is cleared - await assertAttributeValues(t, ex.comboboxSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.comboboxSelector, + 'aria-expanded', + 'false' + ); t.is( await t.context.session @@ -555,12 +721,14 @@ ariaTest('Test escape key press with focus on popup', 'a', 'In grid key press "ESCAPE" should result the "a" in the combobox' ); + } +); - }); - -ariaTest('left arrow from focus on list puts focus on grid and moves cursor right', - exampleFile, 'popup-key-left-arrow', async (t) => { - +ariaTest( + 'left arrow from focus on list puts focus on grid and moves cursor right', + exampleFile, + 'popup-key-left-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the grid const combobox = t.context.session.findElement(By.css(ex.comboboxSelector)); await combobox.sendKeys('a', Key.ARROW_DOWN); @@ -571,46 +739,57 @@ ariaTest('left arrow from focus on list puts focus on grid and moves cursor righ // Check that the active descendent focus is correct let lastrow = ex.numAOptions - 1; - var focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + var focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(lastrow,1), - 'After left arrow sent to popup, aria-activedescendant should be set to the last row, last gricell: ' + gridcellId(lastrow, 1) + gridcellId(lastrow, 1), + 'After left arrow sent to popup, aria-activedescendant should be set to the last row, last gricell: ' + + gridcellId(lastrow, 1) ); - var focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(lastrow,1))) + var focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(lastrow, 1))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(lastrow,1) + '" should have visual focus' + 'Gridcell with id "' + + gridcellId(lastrow, 1) + + '" should have visual focus' ); // Send key "ARROW_LEFT" a second time await combobox.sendKeys(Key.ARROW_LEFT); // Check that the active descendent focus is correct - focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(lastrow,0), - 'After left arrow sent twice to popup, aria-activedescendant should be set to the last row first gridcell: ' + gridcellId(lastrow, 0) + gridcellId(lastrow, 0), + 'After left arrow sent twice to popup, aria-activedescendant should be set to the last row first gridcell: ' + + gridcellId(lastrow, 0) ); - focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(lastrow,0))) + focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(lastrow, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(lastrow,0) + '" should have visual focus' - ); - - }); - - -ariaTest('Right arrow from focus on list puts focus on grid', - exampleFile, 'popup-key-right-arrow', async (t) => { - + 'Gridcell with id "' + + gridcellId(lastrow, 0) + + '" should have visual focus' + ); + } +); + +ariaTest( + 'Right arrow from focus on list puts focus on grid', + exampleFile, + 'popup-key-right-arrow', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the grid const combobox = t.context.session.findElement(By.css(ex.comboboxSelector)); await combobox.sendKeys('a', Key.ARROW_DOWN); @@ -619,64 +798,80 @@ ariaTest('Right arrow from focus on list puts focus on grid', await combobox.sendKeys(Key.ARROW_RIGHT); // Check that the active descendent focus is correct - var focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + var focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(0,1), - 'After right arrow sent to popup, aria-activedescendant should be set to the first row second gridcell element: ' + gridcellId(0, 1) + gridcellId(0, 1), + 'After right arrow sent to popup, aria-activedescendant should be set to the first row second gridcell element: ' + + gridcellId(0, 1) ); - var focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(0,1))) + var focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(0, 1))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(0,1) + '" should have visual focus' + 'Gridcell with id "' + gridcellId(0, 1) + '" should have visual focus' ); // Send key "ARROW_RIGHT" a second time await combobox.sendKeys(Key.ARROW_RIGHT); // Check that the active descendent focus is correct - focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(1,0), - 'After right arrow send twice to popup, aria-activedescendant should be set to the second row first gridcell element: ' + gridcellId(1, 0) + gridcellId(1, 0), + 'After right arrow send twice to popup, aria-activedescendant should be set to the second row first gridcell element: ' + + gridcellId(1, 0) ); - focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(1,0))) + focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(1, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(1,0) + '" should have visual focus' + 'Gridcell with id "' + gridcellId(1, 0) + '" should have visual focus' ); // Send key "ARROW_RIGHT" four more times - await combobox.sendKeys(Key.ARROW_RIGHT, Key.ARROW_RIGHT, Key.ARROW_RIGHT, Key.ARROW_RIGHT); + await combobox.sendKeys( + Key.ARROW_RIGHT, + Key.ARROW_RIGHT, + Key.ARROW_RIGHT, + Key.ARROW_RIGHT + ); // Check that the active descendent focus is correct - focusedId = await t.context.session.findElement(By.css(ex.comboboxSelector)) + focusedId = await t.context.session + .findElement(By.css(ex.comboboxSelector)) .getAttribute('aria-activedescendant'); t.is( focusedId, - gridcellId(0,0), - 'After right arrow to the popup 6 times in total, aria-activedescendant should be back on the first row, first gridcell: ' + gridcellId(0, 0) + gridcellId(0, 0), + 'After right arrow to the popup 6 times in total, aria-activedescendant should be back on the first row, first gridcell: ' + + gridcellId(0, 0) ); - focusedElementClasses = await t.context.session.findElement(By.id(gridcellId(0,0))) + focusedElementClasses = await t.context.session + .findElement(By.id(gridcellId(0, 0))) .getAttribute('class'); t.true( focusedElementClasses.includes(ex.gridcellFocusedClass), - 'Gridcell with id "' + gridcellId(0,0) + '" should have visual focus' + 'Gridcell with id "' + gridcellId(0, 0) + '" should have visual focus' ); + } +); - }); - -ariaTest('Home from focus on list puts focus on grid and moves cursor', - exampleFile, 'popup-key-home', async (t) => { - +ariaTest( + 'Home from focus on list puts focus on grid and moves cursor', + exampleFile, + 'popup-key-home', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the grid const combobox = t.context.session.findElement(By.css(ex.comboboxSelector)); await combobox.sendKeys('a', Key.ARROW_DOWN); @@ -692,13 +887,16 @@ ariaTest('Home from focus on list puts focus on grid and moves cursor', t.is( await combobox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the combobox after one ARROW_HOME key', + 'Focus should be on the combobox after one ARROW_HOME key' ); - }); - -ariaTest('End from focus on list puts focus on grid', - exampleFile, 'popup-key-end', async (t) => { + } +); +ariaTest( + 'End from focus on list puts focus on grid', + exampleFile, + 'popup-key-end', + async (t) => { // Send key "a" then key "ARROW_DOWN" to put the focus on the grid const combobox = t.context.session.findElement(By.css(ex.comboboxSelector)); await combobox.sendKeys('a', Key.ARROW_DOWN); @@ -714,13 +912,16 @@ ariaTest('End from focus on list puts focus on grid', t.is( await combobox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the combobox after on ARROW_END key', + 'Focus should be on the combobox after on ARROW_END key' ); - }); - -ariaTest('Sending character keys while focus is on grid moves focus', - exampleFile, 'popup-key-char', async (t) => { + } +); +ariaTest( + 'Sending character keys while focus is on grid moves focus', + exampleFile, + 'popup-key-char', + async (t) => { // Send key "ARROW_DOWN" to put the focus on the grid const combobox = t.context.session.findElement(By.css(ex.comboboxSelector)); await combobox.sendKeys(Key.ARROW_DOWN); @@ -738,13 +939,16 @@ ariaTest('Sending character keys while focus is on grid moves focus', t.is( await combobox.getAttribute('aria-activedescendant'), '', - 'Focus should be on the combobox after sending a character key while the focus is on the grid', - ); - - }); - -ariaTest.failing('Expected behavior for all other standard single line editing keys', - exampleFile, 'standard-single-line-editing-keys', async (t) => { - t.fail(); - }); - + 'Focus should be on the combobox after sending a character key while the focus is on the grid' + ); + } +); + +ariaTest.failing( + 'Expected behavior for all other standard single line editing keys', + exampleFile, + 'standard-single-line-editing-keys', + async (t) => { + t.fail(); + } +); diff --git a/test/tests/combobox_select-only.js b/test/tests/combobox_select-only.js index 4bbaf076a4..f105251736 100644 --- a/test/tests/combobox_select-only.js +++ b/test/tests/combobox_select-only.js @@ -10,7 +10,7 @@ const exampleFile = 'combobox/combobox-select-only.html'; const ex = { comboSelector: '#combo1', - listboxSelector: '#listbox1' + listboxSelector: '#listbox1', }; // Attributes @@ -27,519 +27,1051 @@ ariaTest('role "option"', exampleFile, 'option-role', async (t) => { await t.context.session.findElement(By.css(ex.comboSelector)).click(); // query listbox children - const options = await t.context.queryElements(t, `${ex.listboxSelector} > div`); - await Promise.all(options.map(async (option) => { - const role = await option.getAttribute('role'); - t.is(role, 'option', 'Immediate descendents of the listbox should have role="option"'); - })); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} > div` + ); + await Promise.all( + options.map(async (option) => { + const role = await option.getAttribute('role'); + t.is( + role, + 'option', + 'Immediate descendents of the listbox should have role="option"' + ); + }) + ); }); -ariaTest('aria-labelledby on combobox', exampleFile, 'combobox-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.comboSelector); -}); - -ariaTest('aria-controls on combobox', exampleFile, 'combobox-aria-controls', async (t) => { - const controlledId = await t.context.session - .findElement(By.css(ex.comboSelector)) - .getAttribute('aria-controls'); - - t.truthy(controlledId, '"aria-controls" should exist on the combobox'); - - const controlledRole = await t.context.session.findElement(By.id(controlledId)).getAttribute('role'); - - t.is(controlledRole, 'listbox', 'The combobox\'s aria-controls attribute should point to a listbox'); -}); - -ariaTest('aria-expanded="false" when closed', exampleFile, 'combobox-aria-expanded', async (t) => { - const expanded = await t.context.session.findElement(By.css(ex.comboSelector)).getAttribute('aria-expanded'); - - t.is(expanded, 'false', 'aria-expanded should be false by default'); -}); - -ariaTest('click opens combobox and sets aria-expanded="true"', exampleFile, 'combobox-aria-expanded', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - - await combobox.click(); - const expanded = await combobox.getAttribute('aria-expanded'); - const popupDisplayed = await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(); - - t.is(expanded, 'true', 'aria-expanded should be true when opened'); - t.true(popupDisplayed, 'listbox should be present after click'); -}); - -ariaTest('"aria-activedescendant" on combobox element', exampleFile, 'combobox-aria-activedescendant', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const firstOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)); - const optionId = await firstOption.getAttribute('id'); - - await combobox.click(); - - await assertAttributeValues(t, ex.comboSelector, 'aria-activedescendant', optionId); -}); - -ariaTest('"aria-selected" attribute on first option', exampleFile, 'option-aria-selected', async (t) => { - const firstOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)); - const secondOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`)); - - t.is(await firstOption.getAttribute('aria-selected'), 'true', 'the first option is selected by default'); - t.is(await secondOption.getAttribute('aria-selected'), 'false', 'other options have aria-selected set to false'); -}); +ariaTest( + 'aria-labelledby on combobox', + exampleFile, + 'combobox-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.comboSelector); + } +); + +ariaTest( + 'aria-controls on combobox', + exampleFile, + 'combobox-aria-controls', + async (t) => { + const controlledId = await t.context.session + .findElement(By.css(ex.comboSelector)) + .getAttribute('aria-controls'); + + t.truthy(controlledId, '"aria-controls" should exist on the combobox'); + + const controlledRole = await t.context.session + .findElement(By.id(controlledId)) + .getAttribute('role'); + + t.is( + controlledRole, + 'listbox', + "The combobox's aria-controls attribute should point to a listbox" + ); + } +); + +ariaTest( + 'aria-expanded="false" when closed', + exampleFile, + 'combobox-aria-expanded', + async (t) => { + const expanded = await t.context.session + .findElement(By.css(ex.comboSelector)) + .getAttribute('aria-expanded'); + + t.is(expanded, 'false', 'aria-expanded should be false by default'); + } +); + +ariaTest( + 'click opens combobox and sets aria-expanded="true"', + exampleFile, + 'combobox-aria-expanded', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + + await combobox.click(); + const expanded = await combobox.getAttribute('aria-expanded'); + const popupDisplayed = await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(); + + t.is(expanded, 'true', 'aria-expanded should be true when opened'); + t.true(popupDisplayed, 'listbox should be present after click'); + } +); + +ariaTest( + '"aria-activedescendant" on combobox element', + exampleFile, + 'combobox-aria-activedescendant', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const firstOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]`) + ); + const optionId = await firstOption.getAttribute('id'); + + await combobox.click(); + + await assertAttributeValues( + t, + ex.comboSelector, + 'aria-activedescendant', + optionId + ); + } +); + +ariaTest( + '"aria-selected" attribute on first option', + exampleFile, + 'option-aria-selected', + async (t) => { + const firstOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]`) + ); + const secondOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`) + ); + + t.is( + await firstOption.getAttribute('aria-selected'), + 'true', + 'the first option is selected by default' + ); + t.is( + await secondOption.getAttribute('aria-selected'), + 'false', + 'other options have aria-selected set to false' + ); + } +); // Behavior // Open listbox -ariaTest('Alt + down arrow opens listbox', exampleFile, 'combobox-key-alt-down-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)).getAttribute('id'); - - // listbox starts collapsed - t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); - - // Send ALT + ARROW_DOWN to the combo - await combobox.sendKeys(Key.chord(Key.ALT, Key.ARROW_DOWN)); - - // Check that the listbox is displayed - t.true(await listbox.isDisplayed(), 'alt + down should show the listbox'); - t.is(await combobox.getAttribute('aria-expanded'), 'true', 'aria-expanded should be true when opened'); - - // the first option should be selected - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'Alt + Down should highlight the first option'); -}); - -ariaTest('Up arrow opens listbox', exampleFile, 'combobox-key-up-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)).getAttribute('id'); - - // listbox starts collapsed - t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); - - // Send ARROW_UP to the combo - await combobox.sendKeys(Key.ARROW_UP); - - // Check that the listbox is displayed - t.true(await listbox.isDisplayed(), 'arrow up should show the listbox'); - t.is(await combobox.getAttribute('aria-expanded'), 'true', 'aria-expanded should be true when opened'); - - // the first option should be selected - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'arrow up should highlight the first option'); -}); - - -ariaTest(' arrow opens listbox', exampleFile, 'combobox-key-down-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)).getAttribute('id'); - - // listbox starts collapsed - t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); - - // Send ARROW_DOWN to the combo - await combobox.sendKeys(Key.ARROW_DOWN); - - // Check that the listbox is displayed - t.true(await listbox.isDisplayed(), 'alt + down should show the listbox'); - t.is(await combobox.getAttribute('aria-expanded'), 'true', 'aria-expanded should be true when opened'); - - // the first option should be selected - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'Down arrow should highlight the first option'); -}); - -ariaTest('Enter opens listbox', exampleFile, 'combobox-key-enter', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)).getAttribute('id'); - - // listbox starts collapsed - t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); - - // Send ENTER to the combo - await combobox.sendKeys(Key.ENTER); - - // Check that the listbox is displayed - t.true(await listbox.isDisplayed(), 'enter should show the listbox'); - - // the first option should be selected - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'enter should highlight the first option'); -}); - -ariaTest('Space opens listbox', exampleFile, 'combobox-key-space', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)).getAttribute('id'); - - // listbox starts collapsed - t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); - - // Send space to the combo - await combobox.sendKeys(' '); - - // Check that the listbox is displayed - t.true(await listbox.isDisplayed(), 'space should show the listbox'); - - // the first option should be selected - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'space should highlight the first option'); -}); - -ariaTest('combobox opens on last highlighted option', exampleFile, 'combobox-key-down-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const secondOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`)).getAttribute('id'); - - // Open, select second option, close - await combobox.sendKeys(' '); - await combobox.sendKeys(Key.ARROW_DOWN); - await combobox.sendKeys(Key.ESCAPE); - - // Open again - await combobox.sendKeys(' '); - - // Check that the listbox is displayed and second option is highlighted - t.true(await listbox.isDisplayed(), 'space should show the listbox'); - t.is(await combobox.getAttribute('aria-activedescendant'), secondOptionId, 'second option should be highlighted'); -}); - -ariaTest('Home opens listbox to first option', exampleFile, 'combobox-key-home', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)).getAttribute('id'); - - // Open, select second option, close - await combobox.sendKeys(' '); - await combobox.sendKeys(Key.ARROW_DOWN); - await combobox.sendKeys(Key.ESCAPE); - - // listbox is collapsed - t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); - - // Send home key to the combo - await combobox.sendKeys(Key.HOME); - - // Check that the listbox is displayed and first option is highlighted - t.true(await listbox.isDisplayed(), 'home should show the listbox'); - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'home should always highlight the first option'); -}); - -ariaTest('End opens listbox to last option', exampleFile, 'combobox-key-end', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const options = await t.context.queryElements(t, `${ex.listboxSelector} [role=option]`); - const lastOptionId = await options[options.length - 1].getAttribute('id'); - - // Send end key to the combo - await combobox.sendKeys(Key.END); - - // Check that the listbox is displayed and first option is highlighted - t.true(await listbox.isDisplayed(), 'end should show the listbox'); - t.is(await combobox.getAttribute('aria-activedescendant'), lastOptionId, 'end should always highlight the last option'); -}); - -ariaTest('character keys open listbox to matching option', exampleFile, 'printable-chars', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const secondOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`)).getAttribute('id'); - - // type "a" - await combobox.sendKeys('a'); - - // Check that the listbox is displayed and the second option is highlighted - // bit of hard-coding here; we know that the second option begins with "a", since the first is a placeholder - t.true(await listbox.isDisplayed(), 'character key should show the listbox'); - t.is(await combobox.getAttribute('aria-activedescendant'), secondOptionId, 'typing "a" should highlight the first option beginning with "a"'); -}); +ariaTest( + 'Alt + down arrow opens listbox', + exampleFile, + 'combobox-key-alt-down-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]`)) + .getAttribute('id'); + + // listbox starts collapsed + t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); + + // Send ALT + ARROW_DOWN to the combo + await combobox.sendKeys(Key.chord(Key.ALT, Key.ARROW_DOWN)); + + // Check that the listbox is displayed + t.true(await listbox.isDisplayed(), 'alt + down should show the listbox'); + t.is( + await combobox.getAttribute('aria-expanded'), + 'true', + 'aria-expanded should be true when opened' + ); + + // the first option should be selected + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'Alt + Down should highlight the first option' + ); + } +); + +ariaTest( + 'Up arrow opens listbox', + exampleFile, + 'combobox-key-up-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]`)) + .getAttribute('id'); + + // listbox starts collapsed + t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); + + // Send ARROW_UP to the combo + await combobox.sendKeys(Key.ARROW_UP); + + // Check that the listbox is displayed + t.true(await listbox.isDisplayed(), 'arrow up should show the listbox'); + t.is( + await combobox.getAttribute('aria-expanded'), + 'true', + 'aria-expanded should be true when opened' + ); + + // the first option should be selected + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'arrow up should highlight the first option' + ); + } +); + +ariaTest( + ' arrow opens listbox', + exampleFile, + 'combobox-key-down-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]`)) + .getAttribute('id'); + + // listbox starts collapsed + t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); + + // Send ARROW_DOWN to the combo + await combobox.sendKeys(Key.ARROW_DOWN); + + // Check that the listbox is displayed + t.true(await listbox.isDisplayed(), 'alt + down should show the listbox'); + t.is( + await combobox.getAttribute('aria-expanded'), + 'true', + 'aria-expanded should be true when opened' + ); + + // the first option should be selected + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'Down arrow should highlight the first option' + ); + } +); + +ariaTest( + 'Enter opens listbox', + exampleFile, + 'combobox-key-enter', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]`)) + .getAttribute('id'); + + // listbox starts collapsed + t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); + + // Send ENTER to the combo + await combobox.sendKeys(Key.ENTER); + + // Check that the listbox is displayed + t.true(await listbox.isDisplayed(), 'enter should show the listbox'); + + // the first option should be selected + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'enter should highlight the first option' + ); + } +); + +ariaTest( + 'Space opens listbox', + exampleFile, + 'combobox-key-space', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]`)) + .getAttribute('id'); + + // listbox starts collapsed + t.false(await listbox.isDisplayed(), 'Listbox should be hidden on load'); + + // Send space to the combo + await combobox.sendKeys(' '); + + // Check that the listbox is displayed + t.true(await listbox.isDisplayed(), 'space should show the listbox'); + + // the first option should be selected + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'space should highlight the first option' + ); + } +); + +ariaTest( + 'combobox opens on last highlighted option', + exampleFile, + 'combobox-key-down-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const secondOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`)) + .getAttribute('id'); + + // Open, select second option, close + await combobox.sendKeys(' '); + await combobox.sendKeys(Key.ARROW_DOWN); + await combobox.sendKeys(Key.ESCAPE); + + // Open again + await combobox.sendKeys(' '); + + // Check that the listbox is displayed and second option is highlighted + t.true(await listbox.isDisplayed(), 'space should show the listbox'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + secondOptionId, + 'second option should be highlighted' + ); + } +); + +ariaTest( + 'Home opens listbox to first option', + exampleFile, + 'combobox-key-home', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]`)) + .getAttribute('id'); + + // Open, select second option, close + await combobox.sendKeys(' '); + await combobox.sendKeys(Key.ARROW_DOWN); + await combobox.sendKeys(Key.ESCAPE); + + // listbox is collapsed + t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); + + // Send home key to the combo + await combobox.sendKeys(Key.HOME); + + // Check that the listbox is displayed and first option is highlighted + t.true(await listbox.isDisplayed(), 'home should show the listbox'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'home should always highlight the first option' + ); + } +); + +ariaTest( + 'End opens listbox to last option', + exampleFile, + 'combobox-key-end', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} [role=option]` + ); + const lastOptionId = await options[options.length - 1].getAttribute('id'); + + // Send end key to the combo + await combobox.sendKeys(Key.END); + + // Check that the listbox is displayed and first option is highlighted + t.true(await listbox.isDisplayed(), 'end should show the listbox'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + lastOptionId, + 'end should always highlight the last option' + ); + } +); + +ariaTest( + 'character keys open listbox to matching option', + exampleFile, + 'printable-chars', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const secondOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`)) + .getAttribute('id'); + + // type "a" + await combobox.sendKeys('a'); + + // Check that the listbox is displayed and the second option is highlighted + // bit of hard-coding here; we know that the second option begins with "a", since the first is a placeholder + t.true( + await listbox.isDisplayed(), + 'character key should show the listbox' + ); + t.is( + await combobox.getAttribute('aria-activedescendant'), + secondOptionId, + 'typing "a" should highlight the first option beginning with "a"' + ); + } +); // Close listbox -ariaTest('click opens and closes listbox', exampleFile, 'test-additional-behavior', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - - await combobox.click(); - t.true(await listbox.isDisplayed(), 'listbox should be present after click'); - - await combobox.click(); - t.false(await listbox.isDisplayed(), 'second click should close listbox'); - t.is(await combobox.getAttribute('aria-expanded'), 'false', 'aria-expanded should be set to false after second click'); -}); - -ariaTest('clicking an option selects and closes', exampleFile, 'test-additional-behavior', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const fourthOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(4)`)); - - await combobox.click(); - const fourthOptionText = await fourthOption.getText(); - t.true(await listbox.isDisplayed(), 'listbox should be present after click'); - - await fourthOption.click(); - t.false(await listbox.isDisplayed(), 'option click should close listbox'); - t.is(await combobox.getText(), fourthOptionText, 'Combobox inner text should match the clicked option'); - t.is(await fourthOption.getAttribute('aria-selected'), 'true', 'Clicked option has aria-selected set to true'); -}); - -ariaTest('Enter closes listbox and selects option', exampleFile, 'listbox-key-enter', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const thirdOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(3)`)); - - // Open, move to third option, hit enter - await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN, Key.ARROW_DOWN); - const thirdOptionText = await thirdOption.getText(); - await combobox.sendKeys(Key.ENTER); - - - // listbox is collapsed and the value is set to the third option - t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); - t.is(await combobox.getAttribute('aria-expanded'), 'false', 'test aria-expanded on combo'); - t.is(await combobox.getText(), thirdOptionText, 'Combobox inner text should match the third option'); - t.is(await thirdOption.getAttribute('aria-selected'), 'true', 'Third option has aria-selected set to true'); -}); - -ariaTest('Space closes listbox and selects option', exampleFile, 'listbox-key-space', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const secondOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`)); - - // Open, move to third option, hit space - await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN); - const secondOptionText = await secondOption.getText(); - await combobox.sendKeys(' '); - - // listbox is collapsed and the value is set to the third option - t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); - t.is(await combobox.getText(), secondOptionText, 'Combobox inner text should match the second option'); - t.is(await secondOption.getAttribute('aria-selected'), 'true', 'Second option has aria-selected set to true'); -}); - -ariaTest('Space closes listbox and selects option', exampleFile, 'listbox-key-alt-up-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const secondOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`)); - - // Open, move to third option, send ALT+UP ARROW - await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN); - const secondOptionText = await secondOption.getText(); - await combobox.sendKeys(Key.chord(Key.ALT, Key.ARROW_UP)); - - // listbox is collapsed and the value is set to the third option - t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); - t.is(await combobox.getText(), secondOptionText, 'Combobox inner text should match the second option'); - t.is(await secondOption.getAttribute('aria-selected'), 'true', 'Second option has aria-selected set to true'); -}); - - -ariaTest('Escape closes listbox without selecting option', exampleFile, 'listbox-key-escape', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - - // Open, move to third option, hit enter - await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN, Key.ARROW_DOWN); - const firstOptionText = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]`)).getText(); - await combobox.sendKeys(Key.ESCAPE); - - // listbox is collapsed and the value is still set to the first option - t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); - t.is(await combobox.getText(), firstOptionText, 'Combobox inner text should match the first option'); -}); - -ariaTest('Tab closes listbox and selects option', exampleFile, 'listbox-key-tab', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const fourthOption = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(4)`)); - - // Open, move to fourth option, hit tab - await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN, Key.ARROW_DOWN, Key.ARROW_DOWN); - const fourthOptionText = await fourthOption.getText(); - await combobox.sendKeys(Key.TAB); - - // listbox is collapsed and the value is set to the fourth option - t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); - t.is(await combobox.getText(), fourthOptionText, 'Combobox inner text should match the second option'); - t.is(await fourthOption.getAttribute('aria-selected'), 'true', 'Fourth option has aria-selected set to true'); -}); +ariaTest( + 'click opens and closes listbox', + exampleFile, + 'test-additional-behavior', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + + await combobox.click(); + t.true( + await listbox.isDisplayed(), + 'listbox should be present after click' + ); + + await combobox.click(); + t.false(await listbox.isDisplayed(), 'second click should close listbox'); + t.is( + await combobox.getAttribute('aria-expanded'), + 'false', + 'aria-expanded should be set to false after second click' + ); + } +); + +ariaTest( + 'clicking an option selects and closes', + exampleFile, + 'test-additional-behavior', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const fourthOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]:nth-child(4)`) + ); + + await combobox.click(); + const fourthOptionText = await fourthOption.getText(); + t.true( + await listbox.isDisplayed(), + 'listbox should be present after click' + ); + + await fourthOption.click(); + t.false(await listbox.isDisplayed(), 'option click should close listbox'); + t.is( + await combobox.getText(), + fourthOptionText, + 'Combobox inner text should match the clicked option' + ); + t.is( + await fourthOption.getAttribute('aria-selected'), + 'true', + 'Clicked option has aria-selected set to true' + ); + } +); + +ariaTest( + 'Enter closes listbox and selects option', + exampleFile, + 'listbox-key-enter', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const thirdOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]:nth-child(3)`) + ); + + // Open, move to third option, hit enter + await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN, Key.ARROW_DOWN); + const thirdOptionText = await thirdOption.getText(); + await combobox.sendKeys(Key.ENTER); + + // listbox is collapsed and the value is set to the third option + t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); + t.is( + await combobox.getAttribute('aria-expanded'), + 'false', + 'test aria-expanded on combo' + ); + t.is( + await combobox.getText(), + thirdOptionText, + 'Combobox inner text should match the third option' + ); + t.is( + await thirdOption.getAttribute('aria-selected'), + 'true', + 'Third option has aria-selected set to true' + ); + } +); + +ariaTest( + 'Space closes listbox and selects option', + exampleFile, + 'listbox-key-space', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const secondOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`) + ); + + // Open, move to third option, hit space + await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN); + const secondOptionText = await secondOption.getText(); + await combobox.sendKeys(' '); + + // listbox is collapsed and the value is set to the third option + t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); + t.is( + await combobox.getText(), + secondOptionText, + 'Combobox inner text should match the second option' + ); + t.is( + await secondOption.getAttribute('aria-selected'), + 'true', + 'Second option has aria-selected set to true' + ); + } +); + +ariaTest( + 'Space closes listbox and selects option', + exampleFile, + 'listbox-key-alt-up-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const secondOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]:nth-child(2)`) + ); + + // Open, move to third option, send ALT+UP ARROW + await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN); + const secondOptionText = await secondOption.getText(); + await combobox.sendKeys(Key.chord(Key.ALT, Key.ARROW_UP)); + + // listbox is collapsed and the value is set to the third option + t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); + t.is( + await combobox.getText(), + secondOptionText, + 'Combobox inner text should match the second option' + ); + t.is( + await secondOption.getAttribute('aria-selected'), + 'true', + 'Second option has aria-selected set to true' + ); + } +); + +ariaTest( + 'Escape closes listbox without selecting option', + exampleFile, + 'listbox-key-escape', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + + // Open, move to third option, hit enter + await combobox.sendKeys(Key.ENTER, Key.ARROW_DOWN, Key.ARROW_DOWN); + const firstOptionText = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]`)) + .getText(); + await combobox.sendKeys(Key.ESCAPE); + + // listbox is collapsed and the value is still set to the first option + t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); + t.is( + await combobox.getText(), + firstOptionText, + 'Combobox inner text should match the first option' + ); + } +); + +ariaTest( + 'Tab closes listbox and selects option', + exampleFile, + 'listbox-key-tab', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const fourthOption = await t.context.session.findElement( + By.css(`${ex.listboxSelector} [role=option]:nth-child(4)`) + ); + + // Open, move to fourth option, hit tab + await combobox.sendKeys( + Key.ENTER, + Key.ARROW_DOWN, + Key.ARROW_DOWN, + Key.ARROW_DOWN + ); + const fourthOptionText = await fourthOption.getText(); + await combobox.sendKeys(Key.TAB); + + // listbox is collapsed and the value is set to the fourth option + t.false(await listbox.isDisplayed(), 'Listbox should be hidden'); + t.is( + await combobox.getText(), + fourthOptionText, + 'Combobox inner text should match the second option' + ); + t.is( + await fourthOption.getAttribute('aria-selected'), + 'true', + 'Fourth option has aria-selected set to true' + ); + } +); // Changing options -ariaTest('Down arrow moves to next option', exampleFile, 'listbox-key-down-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const thirdOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(3)`)).getAttribute('id'); - - // Open, press down arrow - await combobox.click(); - await combobox.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN); - - // Second option is highlighted - t.is(await combobox.getAttribute('aria-activedescendant'), thirdOptionId, 'aria-activedescendant points to the third option after two down arrows'); -}); - -ariaTest('Down arrow does not wrap after last option', exampleFile, 'listbox-key-down-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const lastOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:last-child`)).getAttribute('id'); - - // Open, press end, press down arrow - await combobox.click(); - await combobox.sendKeys(Key.END, Key.ARROW_DOWN); - - // last option is highlighted - t.is(await combobox.getAttribute('aria-activedescendant'), lastOptionId, 'aria-activedescendant points to the last option after end + down arrow'); -}); - -ariaTest('Up arrow does not wrap from first option', exampleFile, 'listbox-key-up-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:first-child`)).getAttribute('id'); - - // Open, press up arrow - await combobox.click(); - await combobox.sendKeys(Key.ARROW_UP); - - // first option is highlighted - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'aria-activedescendant points to the first option after up arrow'); -}); - -ariaTest('Up arrow moves to previous option', exampleFile, 'listbox-key-up-arrow', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const options = await t.context.queryElements(t, `${ex.listboxSelector} [role=option]`); - const optionId = await options[options.length - 2].getAttribute('id'); - - // Open, press end + up arrow - await combobox.click(); - await combobox.sendKeys(Key.END, Key.ARROW_UP); - - // second to last option is highlighted - t.is(await combobox.getAttribute('aria-activedescendant'), optionId, 'aria-activedescendant points to the second-to-last option after end + up arrow'); -}); - -ariaTest('End moves to last option', exampleFile, 'listbox-key-end', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const lastOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:last-child`)).getAttribute('id'); - - // Open, press end - await combobox.click(); - await combobox.sendKeys(Key.END); - - // last option is highlighted - t.is(await combobox.getAttribute('aria-activedescendant'), lastOptionId, 'aria-activedescendant points to the last option after end'); -}); - -ariaTest('End scrolls last option into view', exampleFile, 'test-additional-behavior', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const options = await t.context.queryElements(t, `${ex.listboxSelector} [role=option]`); - - await combobox.click(); - - let listboxBounds = await listbox.getRect(); - let optionBounds = await options[options.length - 1].getRect(); - - t.true(listboxBounds.y + listboxBounds.height - optionBounds.y < 0, 'last option is not initially displayed'); - - await combobox.sendKeys(Key.END); - listboxBounds = await listbox.getRect(); - optionBounds = await options[options.length - 1].getRect(); - - t.true(listboxBounds.y + listboxBounds.height - optionBounds.y >= 0, 'last option is in view after end key'); -}); - -ariaTest('Home moves to first option', exampleFile, 'listbox-key-home', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const firstOptionId = await t.context.session.findElement(By.css(`${ex.listboxSelector} [role=option]:first-child`)).getAttribute('id'); - - // Open, press down a couple times, then home - await combobox.click(); - await combobox.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN, Key.HOME); - - // first option is highlighted - t.is(await combobox.getAttribute('aria-activedescendant'), firstOptionId, 'aria-activedescendant points to the first option after home'); -}); - -ariaTest('PageDown moves 10 options, and does not wrap', exampleFile, 'listbox-key-pagedown', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const options = await t.context.queryElements(t, `${ex.listboxSelector} [role=option]`); - - // Open, press page down - await combobox.click(); - await combobox.sendKeys(Key.PAGE_DOWN); - - // 11th option is highlighted - let optionId = await options[10].getAttribute('id'); - t.is(await combobox.getAttribute('aria-activedescendant'), optionId, 'aria-activedescendant points to the 10th option after pagedown'); - - // last option is highlighted - await combobox.sendKeys(Key.PAGE_DOWN); - optionId = await options[options.length - 1].getAttribute('id'); - t.is(await combobox.getAttribute('aria-activedescendant'), optionId, 'aria-activedescendant points to the last option after second pagedown'); -}); - -ariaTest('PageUp moves up 10 options, and does not wrap', exampleFile, 'listbox-key-pageup', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const options = await t.context.queryElements(t, `${ex.listboxSelector} [role=option]`); - - // Open, press end then page up - await combobox.click(); - await combobox.sendKeys(Key.END, Key.PAGE_UP); - - // 11th-from-last option is highlighted - let optionId = await options[options.length - 11].getAttribute('id'); - t.is(await combobox.getAttribute('aria-activedescendant'), optionId, 'aria-activedescendant points to the 10th-from-last option after end + pageup'); - - // first option is highlighted - await combobox.sendKeys(Key.PAGE_UP); - optionId = await options[0].getAttribute('id'); - t.is(await combobox.getAttribute('aria-activedescendant'), optionId, 'aria-activedescendant points to the first option after second pageup'); -}); - -ariaTest('Multiple single-character presses cycle through options', exampleFile, 'printable-chars', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const options = await t.context.queryElements(t, `${ex.listboxSelector} [role=option]`); - - // Open, then type "a" - await combobox.click(); - - // get indices of matching options - const optionNames = await Promise.all(options.map(async (option) => { - return await option.getText(); - })); - const matchingOps = optionNames - .filter((name) => name[0].toLowerCase() === 'b') - .map((name) => optionNames.indexOf(name)); - - - // type b, check first matching op is highlighted - await combobox.sendKeys('b'); - let matchingId = await options[matchingOps[0]].getAttribute('id'); - t.is(await combobox.getAttribute('aria-activedescendant'), `${matchingId}`, 'aria-activedescendant points to the first option beginning with "b"'); - - // type b again, second matching option is highlighted - await combobox.sendKeys('b'); - matchingId = await options[matchingOps[1]].getAttribute('id'); - t.is(await combobox.getAttribute('aria-activedescendant'), `${matchingId}`, 'aria-activedescendant points to the second option beginning with "b"'); - - // type "b" as many times as there are matching options - // focus should wrap and end up on second option again - const keys = matchingOps.map((op) => 'b'); - await combobox.sendKeys(...keys); - matchingId = await options[matchingOps[1]].getAttribute('id'); - t.is(await combobox.getAttribute('aria-activedescendant'), `${matchingId}`, 'aria-activedescendant points to the second option beginning with "b"'); -}); - -ariaTest('Typing multiple characters refines search', exampleFile, 'printable-chars', async (t) => { - const combobox = await t.context.session.findElement(By.css(ex.comboSelector)); - const options = await t.context.queryElements(t, `${ex.listboxSelector} [role=option]`); - - await combobox.click(); - - // get info about the fourth option - const fourthName = await options[3].getText(); - const fourthId = await options[3].getAttribute('id'); - - // type first letter - await combobox.sendKeys(fourthName[0]); - - // fourth op should not be hightlighted after only first letter - t.not(await combobox.getAttribute('aria-activedescendant'), fourthId, 'The fourth option is not highlighted after typing only the first letter'); - - // type more letters - await combobox.sendKeys(...fourthName.slice(1, 4).split('')); - - // now fourth option should be highlighted - t.is(await combobox.getAttribute('aria-activedescendant'), fourthId, 'The fourth option is highlighted after typing multiple letters'); -}); +ariaTest( + 'Down arrow moves to next option', + exampleFile, + 'listbox-key-down-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const thirdOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]:nth-child(3)`)) + .getAttribute('id'); + + // Open, press down arrow + await combobox.click(); + await combobox.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN); + + // Second option is highlighted + t.is( + await combobox.getAttribute('aria-activedescendant'), + thirdOptionId, + 'aria-activedescendant points to the third option after two down arrows' + ); + } +); + +ariaTest( + 'Down arrow does not wrap after last option', + exampleFile, + 'listbox-key-down-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const lastOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]:last-child`)) + .getAttribute('id'); + + // Open, press end, press down arrow + await combobox.click(); + await combobox.sendKeys(Key.END, Key.ARROW_DOWN); + + // last option is highlighted + t.is( + await combobox.getAttribute('aria-activedescendant'), + lastOptionId, + 'aria-activedescendant points to the last option after end + down arrow' + ); + } +); + +ariaTest( + 'Up arrow does not wrap from first option', + exampleFile, + 'listbox-key-up-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]:first-child`)) + .getAttribute('id'); + + // Open, press up arrow + await combobox.click(); + await combobox.sendKeys(Key.ARROW_UP); + + // first option is highlighted + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'aria-activedescendant points to the first option after up arrow' + ); + } +); + +ariaTest( + 'Up arrow moves to previous option', + exampleFile, + 'listbox-key-up-arrow', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} [role=option]` + ); + const optionId = await options[options.length - 2].getAttribute('id'); + + // Open, press end + up arrow + await combobox.click(); + await combobox.sendKeys(Key.END, Key.ARROW_UP); + + // second to last option is highlighted + t.is( + await combobox.getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant points to the second-to-last option after end + up arrow' + ); + } +); + +ariaTest( + 'End moves to last option', + exampleFile, + 'listbox-key-end', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const lastOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]:last-child`)) + .getAttribute('id'); + + // Open, press end + await combobox.click(); + await combobox.sendKeys(Key.END); + + // last option is highlighted + t.is( + await combobox.getAttribute('aria-activedescendant'), + lastOptionId, + 'aria-activedescendant points to the last option after end' + ); + } +); + +ariaTest( + 'End scrolls last option into view', + exampleFile, + 'test-additional-behavior', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} [role=option]` + ); + + await combobox.click(); + + let listboxBounds = await listbox.getRect(); + let optionBounds = await options[options.length - 1].getRect(); + + t.true( + listboxBounds.y + listboxBounds.height - optionBounds.y < 0, + 'last option is not initially displayed' + ); + + await combobox.sendKeys(Key.END); + listboxBounds = await listbox.getRect(); + optionBounds = await options[options.length - 1].getRect(); + + t.true( + listboxBounds.y + listboxBounds.height - optionBounds.y >= 0, + 'last option is in view after end key' + ); + } +); + +ariaTest( + 'Home moves to first option', + exampleFile, + 'listbox-key-home', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const firstOptionId = await t.context.session + .findElement(By.css(`${ex.listboxSelector} [role=option]:first-child`)) + .getAttribute('id'); + + // Open, press down a couple times, then home + await combobox.click(); + await combobox.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN, Key.HOME); + + // first option is highlighted + t.is( + await combobox.getAttribute('aria-activedescendant'), + firstOptionId, + 'aria-activedescendant points to the first option after home' + ); + } +); + +ariaTest( + 'PageDown moves 10 options, and does not wrap', + exampleFile, + 'listbox-key-pagedown', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} [role=option]` + ); + + // Open, press page down + await combobox.click(); + await combobox.sendKeys(Key.PAGE_DOWN); + + // 11th option is highlighted + let optionId = await options[10].getAttribute('id'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant points to the 10th option after pagedown' + ); + + // last option is highlighted + await combobox.sendKeys(Key.PAGE_DOWN); + optionId = await options[options.length - 1].getAttribute('id'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant points to the last option after second pagedown' + ); + } +); + +ariaTest( + 'PageUp moves up 10 options, and does not wrap', + exampleFile, + 'listbox-key-pageup', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} [role=option]` + ); + + // Open, press end then page up + await combobox.click(); + await combobox.sendKeys(Key.END, Key.PAGE_UP); + + // 11th-from-last option is highlighted + let optionId = await options[options.length - 11].getAttribute('id'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant points to the 10th-from-last option after end + pageup' + ); + + // first option is highlighted + await combobox.sendKeys(Key.PAGE_UP); + optionId = await options[0].getAttribute('id'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant points to the first option after second pageup' + ); + } +); + +ariaTest( + 'Multiple single-character presses cycle through options', + exampleFile, + 'printable-chars', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} [role=option]` + ); + + // Open, then type "a" + await combobox.click(); + + // get indices of matching options + const optionNames = await Promise.all( + options.map(async (option) => { + return await option.getText(); + }) + ); + const matchingOps = optionNames + .filter((name) => name[0].toLowerCase() === 'b') + .map((name) => optionNames.indexOf(name)); + + // type b, check first matching op is highlighted + await combobox.sendKeys('b'); + let matchingId = await options[matchingOps[0]].getAttribute('id'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + `${matchingId}`, + 'aria-activedescendant points to the first option beginning with "b"' + ); + + // type b again, second matching option is highlighted + await combobox.sendKeys('b'); + matchingId = await options[matchingOps[1]].getAttribute('id'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + `${matchingId}`, + 'aria-activedescendant points to the second option beginning with "b"' + ); + + // type "b" as many times as there are matching options + // focus should wrap and end up on second option again + const keys = matchingOps.map((op) => 'b'); + await combobox.sendKeys(...keys); + matchingId = await options[matchingOps[1]].getAttribute('id'); + t.is( + await combobox.getAttribute('aria-activedescendant'), + `${matchingId}`, + 'aria-activedescendant points to the second option beginning with "b"' + ); + } +); + +ariaTest( + 'Typing multiple characters refines search', + exampleFile, + 'printable-chars', + async (t) => { + const combobox = await t.context.session.findElement( + By.css(ex.comboSelector) + ); + const options = await t.context.queryElements( + t, + `${ex.listboxSelector} [role=option]` + ); + + await combobox.click(); + + // get info about the fourth option + const fourthName = await options[3].getText(); + const fourthId = await options[3].getAttribute('id'); + + // type first letter + await combobox.sendKeys(fourthName[0]); + + // fourth op should not be hightlighted after only first letter + t.not( + await combobox.getAttribute('aria-activedescendant'), + fourthId, + 'The fourth option is not highlighted after typing only the first letter' + ); + + // type more letters + await combobox.sendKeys(...fourthName.slice(1, 4).split('')); + + // now fourth option should be highlighted + t.is( + await combobox.getAttribute('aria-activedescendant'), + fourthId, + 'The fourth option is highlighted after typing multiple letters' + ); + } +); diff --git a/test/tests/dialog-modal_datepicker.js b/test/tests/dialog-modal_datepicker.js index 2e7c5d709c..1fd9a93d3f 100644 --- a/test/tests/dialog-modal_datepicker.js +++ b/test/tests/dialog-modal_datepicker.js @@ -23,8 +23,10 @@ const ex = { gridcellSelector: '#example [role="dialog"] table.dates td', currentMonthDateButtons: '#example table.dates td:not(.disabled)', allDates: '#example [role="dialog"] table.dates td', - jan12019Day: '#example [role="dialog"] table.dates td[data-date="2019-01-01"]', - jan22019Day: '#example [role="dialog"] table.dates td[data-date="2019-01-02"]', + jan12019Day: + '#example [role="dialog"] table.dates td[data-date="2019-01-01"]', + jan22019Day: + '#example [role="dialog"] table.dates td[data-date="2019-01-02"]', todayDay: `#example [role="dialog"] table.dates td[data-date="${todayDataDate}"]`, currentlyFocusedDay: '#example [role="dialog"] table.dates td[tabindex="0"]', allFocusableElementsInDialog: [ @@ -34,32 +36,36 @@ const ex = { '#example [role="dialog"] button.prev-year', '#example [role="dialog"] button.prev-month', '#example [role="dialog"] button.next-month', - '#example [role="dialog"] button.next-year' + '#example [role="dialog"] button.next-year', ], prevMonthButton: '#example [role="dialog"] button.prev-month', prevYearButton: '#example [role="dialog"] button.prev-year', nextMonthButton: '#example [role="dialog"] button.next-month', nextYearButton: '#example [role="dialog"] button.next-year', cancelButton: '#example [role="dialog"] button[value="cancel"]', - okButton: '#example [role="dialog"] button[value="ok"]' + okButton: '#example [role="dialog"] button[value="ok"]', }; const clickFirstOfMonth = async function (t) { let today = new Date(); - today.setUTCHours(0,0,0,0); + today.setUTCHours(0, 0, 0, 0); let firstOfMonth = new Date(today); firstOfMonth.setDate(1); let firstOfMonthString = today.toISOString().split('T')[0]; - return t.context.session.findElement(By.css(`[data-date=${firstOfMonthString}]`)).click(); + return t.context.session + .findElement(By.css(`[data-date=${firstOfMonthString}]`)) + .click(); }; const clickToday = async function (t) { let today = new Date(); - today.setUTCHours(0,0,0,0); + today.setUTCHours(0, 0, 0, 0); let todayString = today.toISOString().split('T')[0]; - return t.context.session.findElement(By.css(`[data-date=${todayString}]`)).click(); + return t.context.session + .findElement(By.css(`[data-date=${todayString}]`)) + .click(); }; const setDateToJanFirst2019 = async function (t) { @@ -79,665 +85,1044 @@ const focusMatchesElement = async function (t, selector) { }, t.context.WaitTime); }; - // Button Tests -ariaTest('"aria-label" attribute on button', exampleFile, 'calendar-button-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.buttonSelector); -}); +ariaTest( + '"aria-label" attribute on button', + exampleFile, + 'calendar-button-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.buttonSelector); + } +); // Dialog Tests -ariaTest('role="dialog" attribute on div', exampleFile, 'dialog-role', async (t) => { +ariaTest( + 'role="dialog" attribute on div', + exampleFile, + 'dialog-role', + async (t) => { await assertAriaRoles(t, 'example', 'dialog', 1, 'div'); -}); + } +); -ariaTest('aria-modal="true" on modal', exampleFile, 'dialog-aria-modal', async (t) => { +ariaTest( + 'aria-modal="true" on modal', + exampleFile, + 'dialog-aria-modal', + async (t) => { await assertAttributeValues(t, ex.dialogSelector, 'aria-modal', 'true'); -}); + } +); -ariaTest('aria-label exist on dialog', exampleFile, 'dialog-aria-label', async (t) => { +ariaTest( + 'aria-label exist on dialog', + exampleFile, + 'dialog-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.dialogSelector); -}); + } +); -ariaTest('aria-live="polite" on keyboard support message', exampleFile, 'dialog-aria-live', async (t) => { +ariaTest( + 'aria-live="polite" on keyboard support message', + exampleFile, + 'dialog-aria-live', + async (t) => { await assertAttributeValues(t, ex.messageSelector, 'aria-live', 'polite'); -}); + } +); -ariaTest('"aria-label" exists on control buttons', exampleFile, 'change-date-button-aria-label', async (t) => { +ariaTest( + '"aria-label" exists on control buttons', + exampleFile, + 'change-date-button-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.controlButtons); -}); - -ariaTest('aria-live="polite" on dialog header', exampleFile, 'change-date-aria-live', async (t) => { - await assertAttributeValues(t, `${ex.dialogSelector} h2`, 'aria-live', 'polite'); -}); + } +); + +ariaTest( + 'aria-live="polite" on dialog header', + exampleFile, + 'change-date-aria-live', + async (t) => { + await assertAttributeValues( + t, + `${ex.dialogSelector} h2`, + 'aria-live', + 'polite' + ); + } +); ariaTest('grid role on table element', exampleFile, 'grid-role', async (t) => { - await assertAriaRoles(t, 'example', 'grid', 1, 'table'); + await assertAriaRoles(t, 'example', 'grid', 1, 'table'); }); -ariaTest('aria-labelledby on grid element', exampleFile, 'grid-aria-labelledby', async (t) => { +ariaTest( + 'aria-labelledby on grid element', + exampleFile, + 'grid-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.gridSelector); -}); + } +); + +ariaTest( + 'Roving tab index on dates in gridcell', + exampleFile, + 'gridcell-button-tabindex', + async (t) => { + await setDateToJanFirst2019(t); + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); -ariaTest('Roving tab index on dates in gridcell', exampleFile, 'gridcell-button-tabindex', async (t) => { - await setDateToJanFirst2019(t); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let focusableButtons = await t.context.queryElements( + t, + ex.currentMonthDateButtons + ); + let allButtons = await t.context.queryElements(t, ex.allDates); + + // test only one element has tabindex="0" + for (let tabbableEl = 0; tabbableEl < 30; tabbableEl++) { + let dateSelected = await focusableButtons[tabbableEl].getText(); + + for (let el = 0; el < allButtons.length; el++) { + let date = await allButtons[el].getText(); + let disabled = (await allButtons[el].getAttribute('class')).includes( + 'disabled' + ); + let tabindex = dateSelected === date && !disabled ? '0' : '-1'; + + t.is( + await allButtons[el].getAttribute('tabindex'), + tabindex, + 'focus is on day ' + + (tabbableEl + 1) + + ' therefore the button number ' + + el + + ' should have tab index set to: ' + + tabindex + ); + } + + // Send the tabindex="0" element the appropriate key to switch focus to the next element + await focusableButtons[tabbableEl].sendKeys(Key.ARROW_RIGHT); + } + } +); - let focusableButtons = await t.context.queryElements(t, ex.currentMonthDateButtons); - let allButtons = await t.context.queryElements(t, ex.allDates); +ariaTest( + 'aria-selected on selected date', + exampleFile, + 'gridcell-button-aria-selected', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await assertAttributeDNE(t, ex.allDates, 'aria-selected'); - // test only one element has tabindex="0" - for (let tabbableEl = 0; tabbableEl < 30; tabbableEl++) { - let dateSelected = await focusableButtons[tabbableEl].getText(); + await setDateToJanFirst2019(t); + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await assertAttributeValues(t, ex.jan12019Day, 'aria-selected', 'true'); + + let selectedButtons = await t.context.queryElements( + t, + `${ex.allDates}[aria-selected="true"]` + ); - for (let el = 0; el < allButtons.length; el++) { - let date = await allButtons[el].getText(); - let disabled = (await allButtons[el].getAttribute('class')).includes('disabled'); - let tabindex = dateSelected === date && !disabled ? '0' : '-1'; + t.is( + selectedButtons.length, + 1, + 'after setting date in box, only one button should have aria-selected' + ); + + await t.context.session.findElement(By.css(ex.jan22019Day)).click(); + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await assertAttributeValues(t, ex.jan22019Day, 'aria-selected', 'true'); + + selectedButtons = await t.context.queryElements( + t, + `${ex.allDates}[aria-selected="true"]` + ); + + t.is( + selectedButtons.length, + 1, + 'after clicking a date and re-opening datepicker, only one button should have aria-selected' + ); + } +); + +// Keyboard + +ariaTest( + 'ENTER to open datepicker', + exampleFile, + 'button-space-return', + async (t) => { + let chooseDateButton = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); + chooseDateButton.sendKeys(Key.ENTER); + + t.not( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending ENTER to the "choose date" button, the calendar dialog should open' + ); + } +); + +ariaTest( + 'SPACE to open datepicker', + exampleFile, + 'button-space-return', + async (t) => { + let chooseDateButton = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); + chooseDateButton.sendKeys(' '); + + t.not( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending SPACE to the "choose date" button, the calendar dialog should open' + ); + } +); + +ariaTest( + 'Sending key ESC when focus is in dialog closes dialog', + exampleFile, + 'dialog-esc', + async (t) => { + let chooseDateButton = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); + + for (let i = 0; i < ex.allFocusableElementsInDialog.length; i++) { + await chooseDateButton.sendKeys(Key.ENTER); + let el = t.context.session.findElement( + By.css(ex.allFocusableElementsInDialog[i]) + ); + await el.sendKeys(Key.ESCAPE); t.is( - await allButtons[el].getAttribute('tabindex'), - tabindex, - 'focus is on day ' + (tabbableEl + 1) + ' therefore the button number ' + - el + ' should have tab index set to: ' + tabindex + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending ESC to element "' + + ex.allFocusableElementsInDialog[i] + + '" in the dialog, the calendar dialog should close' + ); + + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + '', + 'After sending ESC to element "' + + ex.allFocusableElementsInDialog[i] + + '" in the dialog, no date should be selected' + ); + } + } +); + +ariaTest( + 'Tab should go through all tabbable items, then loop', + exampleFile, + 'dialog-tab', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + + for (let itemSelector of ex.allFocusableElementsInDialog) { + t.true( + await focusMatchesElement(t, itemSelector), + 'Focus should be on: ' + itemSelector ); + + await t.context.session + .findElement(By.css(itemSelector)) + .sendKeys(Key.TAB); } - // Send the tabindex="0" element the appropriate key to switch focus to the next element - await focusableButtons[tabbableEl].sendKeys(Key.ARROW_RIGHT); + t.true( + await focusMatchesElement(t, ex.allFocusableElementsInDialog[0]), + 'After tabbing through all items, focus should return to: ' + + ex.allFocusableElementsInDialog[0] + ); } -}); +); -ariaTest('aria-selected on selected date', exampleFile, 'gridcell-button-aria-selected', async (t) => { +ariaTest( + 'Shift+tab should send focus backwards through dialog, then loop', + exampleFile, + 'dialog-shift-tab', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await assertAttributeDNE(t, ex.allDates, 'aria-selected'); + await t.context.session + .findElement(By.css(ex.allFocusableElementsInDialog[0])) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - await setDateToJanFirst2019(t); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await assertAttributeValues(t, ex.jan12019Day, 'aria-selected', 'true'); + let lastIndex = ex.allFocusableElementsInDialog.length - 1; + for (let i = lastIndex; i >= 0; i--) { + t.true( + await focusMatchesElement(t, ex.allFocusableElementsInDialog[i]), + 'Focus should be on: ' + ex.allFocusableElementsInDialog[i] + ); - let selectedButtons = await t.context.queryElements(t, `${ex.allDates}[aria-selected="true"]`); + await t.context.session + .findElement(By.css(ex.allFocusableElementsInDialog[i])) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + } + } +); - t.is( - selectedButtons.length, - 1, - 'after setting date in box, only one button should have aria-selected' - ); +ariaTest( + 'ENTER to buttons change calendar and date in focus', + exampleFile, + 'month-year-button-space-return', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + // By default, focus will be on todays date. + let day = new Date(); - await t.context.session.findElement(By.css(ex.jan22019Day)).click(); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await assertAttributeValues(t, ex.jan22019Day, 'aria-selected', 'true'); + // send enter to next month button - selectedButtons = await t.context.queryElements(t, `${ex.allDates}[aria-selected="true"]`); + await t.context.session + .findElement(By.css(ex.nextMonthButton)) + .sendKeys(Key.ENTER); + day.setMonth(day.getMonth() + 1); + let dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); - t.is( - selectedButtons.length, - 1, - 'after clicking a date and re-opening datepicker, only one button should have aria-selected' - ); + t.is( + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus + ); -}); + // send enter to next year button + await t.context.session + .findElement(By.css(ex.nextYearButton)) + .sendKeys(Key.ENTER); + day.setFullYear(day.getFullYear() + 1); + dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); + t.is( + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, then next year button date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus + ); -// Keyboard + // Send enter to previous month button + await t.context.session + .findElement(By.css(ex.prevMonthButton)) + .sendKeys(Key.ENTER); + day.setMonth(day.getMonth() - 1); + dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); -ariaTest('ENTER to open datepicker', exampleFile, 'button-space-return', async (t) => { - let chooseDateButton = await t.context.session.findElement(By.css(ex.buttonSelector)); - chooseDateButton.sendKeys(Key.ENTER); + t.is( + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, then next year button date, then previous month button, date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus + ); - t.not( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending ENTER to the "choose date" button, the calendar dialog should open' - ); -}); + // Send enter to previous year button -ariaTest('SPACE to open datepicker', exampleFile, 'button-space-return', async (t) => { - let chooseDateButton = await t.context.session.findElement(By.css(ex.buttonSelector)); - chooseDateButton.sendKeys(' '); + await t.context.session + .findElement(By.css(ex.prevYearButton)) + .sendKeys(Key.ENTER); + day.setFullYear(day.getFullYear() - 1); + dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); - t.not( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending SPACE to the "choose date" button, the calendar dialog should open' - ); -}); + t.is( + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, then next year button date, then previous month button, then previous year button, date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus + ); + } +); -ariaTest('Sending key ESC when focus is in dialog closes dialog', exampleFile, 'dialog-esc', async (t) => { +ariaTest( + 'SPACE to buttons change calendar and date in focus', + exampleFile, + 'month-year-button-space-return', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + // By default, focus will be on todays date. + let day = new Date(); - let chooseDateButton = await t.context.session.findElement(By.css(ex.buttonSelector)); + // send space to next month button - for (let i = 0; i < ex.allFocusableElementsInDialog.length; i++) { + await t.context.session + .findElement(By.css(ex.nextMonthButton)) + .sendKeys(Key.SPACE); + day.setMonth(day.getMonth() + 1); + let dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); - await chooseDateButton.sendKeys(Key.ENTER); - let el = t.context.session.findElement(By.css(ex.allFocusableElementsInDialog[i])); - await el.sendKeys(Key.ESCAPE); + t.is( + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus + ); + + // send space to next year button + await t.context.session + .findElement(By.css(ex.nextYearButton)) + .sendKeys(Key.SPACE); + day.setFullYear(day.getFullYear() + 1); + dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending ESC to element "' + ex.allFocusableElementsInDialog[i] + '" in the dialog, the calendar dialog should close' + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, then next year button date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus ); + // Send space to previous month button + await t.context.session + .findElement(By.css(ex.prevMonthButton)) + .sendKeys(Key.SPACE); + day.setMonth(day.getMonth() - 1); + dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); + t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - '', - 'After sending ESC to element "' + ex.allFocusableElementsInDialog[i] + '" in the dialog, no date should be selected' + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, then next year button date, then previous month button, date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus ); - } -}); -ariaTest('Tab should go through all tabbable items, then loop', exampleFile, 'dialog-tab', async (t) => { + // Send space to previous year button - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await t.context.session + .findElement(By.css(ex.prevYearButton)) + .sendKeys(Key.SPACE); + day.setFullYear(day.getFullYear() - 1); + dayInFocus = await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'); - for (let itemSelector of ex.allFocusableElementsInDialog) { - t.true( - await focusMatchesElement(t, itemSelector), - 'Focus should be on: ' + itemSelector + t.is( + dayInFocus, + day.toISOString().split('T')[0], + 'After selected next month button, then next year button date, then previous month button, then previous year button, date should be ' + + day.toISOString().split('T')[0] + + ' but found: ' + + dayInFocus + ); + } +); + +ariaTest( + 'SPACE or RETURN selects date in focus', + exampleFile, + 'grid-space-return', + async (t) => { + // By default, focus will be on todays date. + let day = new Date(); + + await t.context.session + .findElement(By.css(ex.buttonSelector)) + .sendKeys(Key.ENTER); + await t.context.session + .findElement(By.css(ex.todayDay)) + .sendKeys(Key.ENTER); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, + "ENTER sent to today's date button should select date" ); - await t.context.session.findElement(By.css(itemSelector)).sendKeys(Key.TAB); + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await t.context.session + .findElement(By.css(ex.todayDay)) + .sendKeys(Key.ARROW_RIGHT); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(' '); + + day.setDate(day.getDate() + 1); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, + "SPACE sent to tomorrow's date button should select tomorrow" + ); } +); - t.true( - await focusMatchesElement(t, ex.allFocusableElementsInDialog[0]), - 'After tabbing through all items, focus should return to: ' + ex.allFocusableElementsInDialog[0] - ); -}); +ariaTest( + 'UP ARROW moves date up by week', + exampleFile, + 'grid-up-arrow', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); -ariaTest('Shift+tab should send focus backwards through dialog, then loop', exampleFile, 'dialog-shift-tab', async (t) => { - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + for (let i = 1; i <= 5; i++) { + // Send up arrow to key + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_UP); - await t.context.session.findElement(By.css(ex.allFocusableElementsInDialog[0])) - .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + day.setDate(day.getDate() - 7); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'After sending ' + + i + + ' UP ARROWS to focused date, the focused date should be: ' + + day.toISOString().split('T')[0] + ); + } + } +); - let lastIndex = ex.allFocusableElementsInDialog.length - 1; - for (let i = lastIndex; i >= 0; i--) { - t.true( - await focusMatchesElement(t, ex.allFocusableElementsInDialog[i]), - 'Focus should be on: ' + ex.allFocusableElementsInDialog[i] - ); +ariaTest( + 'DOWN ARROW moves date down by week', + exampleFile, + 'grid-down-arrow', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); - await t.context.session.findElement(By.css(ex.allFocusableElementsInDialog[i])) - .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + for (let i = 1; i <= 5; i++) { + // Send up arrow to key + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_DOWN); + + day.setDate(day.getDate() + 7); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'After sending ' + + i + + ' DOWN ARROWS to focused date, the focused date should be: ' + + day.toISOString().split('T')[0] + ); + } } -}); +); -ariaTest('ENTER to buttons change calendar and date in focus', exampleFile, 'month-year-button-space-return', async (t) => { - - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - // By default, focus will be on todays date. - let day = new Date(); - - // send enter to next month button - - await t.context.session.findElement(By.css(ex.nextMonthButton)).sendKeys(Key.ENTER); - day.setMonth(day.getMonth() + 1); - let dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); - - // send enter to next year button - await t.context.session.findElement(By.css(ex.nextYearButton)).sendKeys(Key.ENTER); - day.setFullYear(day.getFullYear() + 1); - dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, then next year button date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); - - // Send enter to previous month button - await t.context.session.findElement(By.css(ex.prevMonthButton)).sendKeys(Key.ENTER); - day.setMonth(day.getMonth() - 1); - dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, then next year button date, then previous month button, date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); - - // Send enter to previous year button - - await t.context.session.findElement(By.css(ex.prevYearButton)).sendKeys(Key.ENTER); - day.setFullYear(day.getFullYear() - 1); - dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, then next year button date, then previous month button, then previous year button, date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); -}); +ariaTest( + 'RIGHT ARROW moves date greater by one', + exampleFile, + 'grid-right-arrow', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); -ariaTest('SPACE to buttons change calendar and date in focus', exampleFile, 'month-year-button-space-return', async (t) => { - - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - // By default, focus will be on todays date. - let day = new Date(); - - // send space to next month button - - await t.context.session.findElement(By.css(ex.nextMonthButton)).sendKeys(Key.SPACE); - day.setMonth(day.getMonth() + 1); - let dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); - - // send space to next year button - await t.context.session.findElement(By.css(ex.nextYearButton)).sendKeys(Key.SPACE); - day.setFullYear(day.getFullYear() + 1); - dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, then next year button date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); - - // Send space to previous month button - await t.context.session.findElement(By.css(ex.prevMonthButton)).sendKeys(Key.SPACE); - day.setMonth(day.getMonth() - 1); - dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, then next year button date, then previous month button, date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); - - // Send space to previous year button - - await t.context.session.findElement(By.css(ex.prevYearButton)).sendKeys(Key.SPACE); - day.setFullYear(day.getFullYear() - 1); - dayInFocus = await t.context.session - .findElement(By.css(ex.currentlyFocusedDay)) - .getAttribute('data-date'); - - t.is( - dayInFocus, - day.toISOString().split('T')[0], - 'After selected next month button, then next year button date, then previous month button, then previous year button, date should be ' + day.toISOString().split('T')[0] + ' but found: ' + dayInFocus - ); -}); + for (let i = 1; i <= 31; i++) { + // Send up arrow to key + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_RIGHT); -ariaTest('SPACE or RETURN selects date in focus', exampleFile, 'grid-space-return', async (t) => { - - // By default, focus will be on todays date. - let day = new Date(); - - await t.context.session.findElement(By.css(ex.buttonSelector)).sendKeys(Key.ENTER); - await t.context.session.findElement(By.css(ex.todayDay)).sendKeys(Key.ENTER); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, - 'ENTER sent to today\'s date button should select date' - ); - - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.todayDay)).sendKeys(Key.ARROW_RIGHT); - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(' '); - - day.setDate(day.getDate() + 1); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, - 'SPACE sent to tomorrow\'s date button should select tomorrow' - ); -}); + day.setDate(day.getDate() + 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'After sending ' + + i + + ' RIGHT ARROWS to focused date, the focused date should be: ' + + day.toISOString().split('T')[0] + ); + } + } +); -ariaTest('UP ARROW moves date up by week', exampleFile, 'grid-up-arrow', async (t) => { +ariaTest( + 'LEFT ARROW moves date previous one', + exampleFile, + 'grid-left-arrow', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); + for (let i = 1; i <= 31; i++) { + // Send up arrow to key + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_LEFT); - for (let i = 1; i <= 5; i++) { - // Send up arrow to key - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_UP); + day.setDate(day.getDate() - 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'After sending ' + + i + + ' LEFT ARROWS to focused date, the focused date should be: ' + + day.toISOString().split('T')[0] + ); + } + } +); + +ariaTest( + 'Key HOME sends focus to beginning of row', + exampleFile, + 'grid-home', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); + + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.HOME); + day.setDate(day.getDate() - day.getDay()); // getDay returns day of week + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending HOME should move focus to Sunday: ' + + day.toISOString().split('T')[0] + ); - day.setDate(day.getDate() - 7); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.HOME); t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), day.toISOString().split('T')[0], - 'After sending ' + i + ' UP ARROWS to focused date, the focused date should be: ' + day.toISOString().split('T')[0] + 'Sending HOME to Sunday should not move focus from:' + + day.toISOString().split('T')[0] ); } -}); +); -ariaTest('DOWN ARROW moves date down by week', exampleFile, 'grid-down-arrow', async (t) => { +ariaTest( + 'Key END sends focus to end of row', + exampleFile, + 'grid-end', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.END); - for (let i = 1; i <= 5; i++) { - // Send up arrow to key - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_DOWN); + day.setDate(day.getDate() + (6 - day.getDay())); // getDay returns day of week + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending END should move focus to Saturday: ' + + day.toISOString().split('T')[0] + ); - day.setDate(day.getDate() + 7); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.END); t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), day.toISOString().split('T')[0], - 'After sending ' + i + ' DOWN ARROWS to focused date, the focused date should be: ' + day.toISOString().split('T')[0] + 'Sending END to Saturday should not move focus from:' + + day.toISOString().split('T')[0] ); } -}); - -ariaTest('RIGHT ARROW moves date greater by one', exampleFile, 'grid-right-arrow', async (t) => { +); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); +ariaTest( + 'Sending PAGE UP moves focus by back month', + exampleFile, + 'grid-pageup', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); - for (let i = 1; i <= 31; i++) { - // Send up arrow to key - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_RIGHT); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.PAGE_UP); + day.setMonth(day.getMonth() - 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending PAGE UP should move focus back by month: ' + + day.toISOString().split('T')[0] + ); - day.setDate(day.getDate() + 1); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.PAGE_UP); + day.setMonth(day.getMonth() - 1); t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), day.toISOString().split('T')[0], - 'After sending ' + i + ' RIGHT ARROWS to focused date, the focused date should be: ' + day.toISOString().split('T')[0] + 'Sending PAGE UP should move focus back by month, again:' + + day.toISOString().split('T')[0] ); } -}); +); + +ariaTest( + 'Sending SHIFT+PAGE UP moves focus back by year', + exampleFile, + 'grid-shift-pageup', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); -ariaTest('LEFT ARROW moves date previous one', exampleFile, 'grid-left-arrow', async (t) => { + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.chord(Key.SHIFT, Key.PAGE_UP)); + day.setFullYear(day.getFullYear() - 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending SHIFT+PAGE UP should move focus back by year: ' + + day.toISOString().split('T')[0] + ); + + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.chord(Key.SHIFT, Key.PAGE_UP)); + day.setFullYear(day.getFullYear() - 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending SHIFT+PAGE UP should move focus back by year, again:' + + day.toISOString().split('T')[0] + ); + } +); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); +ariaTest( + 'Sending PAGE DOWN moves focus back by month', + exampleFile, + 'grid-pagedown', + async (t) => { + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + let day = new Date(); - for (let i = 1; i <= 31; i++) { - // Send up arrow to key - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_LEFT); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.PAGE_DOWN); + day.setMonth(day.getMonth() + 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending PAGE UP should move focus forward by month: ' + + day.toISOString().split('T')[0] + ); - day.setDate(day.getDate() - 1); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.PAGE_DOWN); + day.setMonth(day.getMonth() + 1); t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), day.toISOString().split('T')[0], - 'After sending ' + i + ' LEFT ARROWS to focused date, the focused date should be: ' + day.toISOString().split('T')[0] + 'Sending PAGE UP should move focus forward by month, again:' + + day.toISOString().split('T')[0] ); } -}); +); -ariaTest('Key HOME sends focus to beginning of row', exampleFile, 'grid-home', async (t) => { +ariaTest( + 'Sending SHIFT+PAGE DOWN moves focus back by year', + exampleFile, + 'grid-shift-pagedown', + async (t) => { await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.HOME); - day.setDate(day.getDate() - day.getDay()); // getDay returns day of week - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending HOME should move focus to Sunday: ' + day.toISOString().split('T')[0] - ); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.HOME); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending HOME to Sunday should not move focus from:' + day.toISOString().split('T')[0] - ); -}); + let day = new Date(); -ariaTest('Key END sends focus to end of row', exampleFile, 'grid-end', async (t) => { + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.chord(Key.SHIFT, Key.PAGE_DOWN)); + day.setFullYear(day.getFullYear() + 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending SHIFT+PAGE UP should move focus forward by year: ' + + day.toISOString().split('T')[0] + ); + + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.chord(Key.SHIFT, Key.PAGE_DOWN)); + day.setFullYear(day.getFullYear() + 1); + t.is( + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .getAttribute('data-date'), + day.toISOString().split('T')[0], + 'Sending SHIFT+PAGE UP should move focus forward by year, again:' + + day.toISOString().split('T')[0] + ); + } +); + +ariaTest( + 'ENTER on cancel button does not select date', + exampleFile, + 'okay-cancel-button-space-return', + async (t) => { await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.END); - - day.setDate(day.getDate() + (6 - day.getDay())); // getDay returns day of week - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending END should move focus to Saturday: ' + day.toISOString().split('T')[0] - ); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.END); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending END to Saturday should not move focus from:' + day.toISOString().split('T')[0] - ); -}); + await t.context.session + .findElement(By.css(ex.cancelButton)) + .sendKeys(Key.ENTER); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + '', + 'ENTER sent to cancel should not set a date' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending ENDER to the "cancel" button, the calendar dialog should close' + ); -ariaTest('Sending PAGE UP moves focus by back month', exampleFile, 'grid-pageup', async (t) => { + await setDateToJanFirst2019(t); await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.PAGE_UP); - day.setMonth(day.getMonth() - 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending PAGE UP should move focus back by month: ' + day.toISOString().split('T')[0] - ); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.PAGE_UP); - day.setMonth(day.getMonth() - 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending PAGE UP should move focus back by month, again:' + day.toISOString().split('T')[0] - ); -}); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_RIGHT); + await t.context.session + .findElement(By.css(ex.cancelButton)) + .sendKeys(Key.ENTER); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + '1/1/2019', + 'ENTER send to cancel should not change date' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending ENTER to the "cancel" button, the calendar dialog should close' + ); + } +); -ariaTest('Sending SHIFT+PAGE UP moves focus back by year', exampleFile, 'grid-shift-pageup', async (t) => { +ariaTest( + 'SPACE on cancel button does not select date', + exampleFile, + 'okay-cancel-button-space-return', + async (t) => { await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.chord(Key.SHIFT, Key.PAGE_UP)); - day.setFullYear(day.getFullYear() - 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending SHIFT+PAGE UP should move focus back by year: ' + day.toISOString().split('T')[0] - ); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.chord(Key.SHIFT, Key.PAGE_UP)); - day.setFullYear(day.getFullYear() - 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending SHIFT+PAGE UP should move focus back by year, again:' + day.toISOString().split('T')[0] - ); -}); + await t.context.session + .findElement(By.css(ex.cancelButton)) + .sendKeys(Key.SPACE); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + '', + 'SPACE sent to cancel should not set a date' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending SPACE to the "cancel" button, the calendar dialog should close' + ); -ariaTest('Sending PAGE DOWN moves focus back by month', exampleFile, 'grid-pagedown', async (t) => { + await setDateToJanFirst2019(t); await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.PAGE_DOWN); - day.setMonth(day.getMonth() + 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending PAGE UP should move focus forward by month: ' + day.toISOString().split('T')[0] - ); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.PAGE_DOWN); - day.setMonth(day.getMonth() + 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending PAGE UP should move focus forward by month, again:' + day.toISOString().split('T')[0] - ); -}); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_RIGHT); + await t.context.session + .findElement(By.css(ex.cancelButton)) + .sendKeys(Key.SPACE); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + '1/1/2019', + 'SPACE send to cancel should not change date' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending SPACE to the "cancel" button, the calendar dialog should close' + ); + } +); + +ariaTest( + 'ENTER on ok button does selects date', + exampleFile, + 'okay-cancel-button-space-return', + async (t) => { + let day = new Date(); -ariaTest('Sending SHIFT+PAGE DOWN moves focus back by year', exampleFile, 'grid-shift-pagedown', async (t) => { await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - let day = new Date(); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.chord(Key.SHIFT, Key.PAGE_DOWN)); - day.setFullYear(day.getFullYear() + 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending SHIFT+PAGE UP should move focus forward by year: ' + day.toISOString().split('T')[0] - ); - - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.chord(Key.SHIFT, Key.PAGE_DOWN)); - day.setFullYear(day.getFullYear() + 1); - t.is( - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).getAttribute('data-date'), - day.toISOString().split('T')[0], - 'Sending SHIFT+PAGE UP should move focus forward by year, again:' + day.toISOString().split('T')[0] - ); -}); + await t.context.session + .findElement(By.css(ex.okButton)) + .sendKeys(Key.ENTER); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, + 'ENTER sent to ok button should set a date' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending ENTER to the "ok" button, the calendar dialog should close' + ); -ariaTest('ENTER on cancel button does not select date', exampleFile, 'okay-cancel-button-space-return', async (t) => { - - - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.cancelButton)).sendKeys(Key.ENTER); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - '', - 'ENTER sent to cancel should not set a date' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending ENDER to the "cancel" button, the calendar dialog should close' - ); - - await setDateToJanFirst2019(t); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_RIGHT); - await t.context.session.findElement(By.css(ex.cancelButton)).sendKeys(Key.ENTER); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - '1/1/2019', - 'ENTER send to cancel should not change date' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending ENTER to the "cancel" button, the calendar dialog should close' - ); -}); + await setDateToJanFirst2019(t); + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_RIGHT); + await t.context.session + .findElement(By.css(ex.okButton)) + .sendKeys(Key.ENTER); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + '1/2/2019', + 'ENTER send to ok should not change date to Jan 2nd' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending ENTER to the "cancel" button, the calendar dialog should close' + ); + } +); -ariaTest('SPACE on cancel button does not select date', exampleFile, 'okay-cancel-button-space-return', async (t) => { - - - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.cancelButton)).sendKeys(Key.SPACE); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - '', - 'SPACE sent to cancel should not set a date' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending SPACE to the "cancel" button, the calendar dialog should close' - ); - - await setDateToJanFirst2019(t); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_RIGHT); - await t.context.session.findElement(By.css(ex.cancelButton)).sendKeys(Key.SPACE); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - '1/1/2019', - 'SPACE send to cancel should not change date' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending SPACE to the "cancel" button, the calendar dialog should close' - ); -}); +ariaTest( + 'SPACE on ok button does selects date', + exampleFile, + 'okay-cancel-button-space-return', + async (t) => { + let day = new Date(); -ariaTest('ENTER on ok button does selects date', exampleFile, 'okay-cancel-button-space-return', async (t) => { - - - let day = new Date(); - - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.okButton)).sendKeys(Key.ENTER); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, - 'ENTER sent to ok button should set a date' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending ENTER to the "ok" button, the calendar dialog should close' - ); - - await setDateToJanFirst2019(t); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_RIGHT); - await t.context.session.findElement(By.css(ex.okButton)).sendKeys(Key.ENTER); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - '1/2/2019', - 'ENTER send to ok should not change date to Jan 2nd' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending ENTER to the "cancel" button, the calendar dialog should close' - ); -}); + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await t.context.session + .findElement(By.css(ex.okButton)) + .sendKeys(Key.SPACE); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, + 'SPACE sent to ok button should set a date' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending SPACE to the "ok" button, the calendar dialog should close' + ); -ariaTest('SPACE on ok button does selects date', exampleFile, 'okay-cancel-button-space-return', async (t) => { - - - let day = new Date(); - - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.okButton)).sendKeys(Key.SPACE); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - `${day.getMonth() + 1}/${day.getDate()}/${day.getFullYear()}`, - 'SPACE sent to ok button should set a date' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending SPACE to the "ok" button, the calendar dialog should close' - ); - - await setDateToJanFirst2019(t); - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - await t.context.session.findElement(By.css(ex.currentlyFocusedDay)).sendKeys(Key.ARROW_RIGHT); - await t.context.session.findElement(By.css(ex.okButton)).sendKeys(Key.SPACE); - t.is( - await t.context.session.findElement(By.css(ex.inputSelector)).getAttribute('value'), - '1/2/2019', - 'SPACE send to ok should not change date to Jan 2nd' - ); - t.is( - await t.context.session.findElement(By.css(ex.dialogSelector)).getCssValue('display'), - 'none', - 'After sending SPACE to the "cancel" button, the calendar dialog should close' - ); -}); + await setDateToJanFirst2019(t); + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + await t.context.session + .findElement(By.css(ex.currentlyFocusedDay)) + .sendKeys(Key.ARROW_RIGHT); + await t.context.session + .findElement(By.css(ex.okButton)) + .sendKeys(Key.SPACE); + t.is( + await t.context.session + .findElement(By.css(ex.inputSelector)) + .getAttribute('value'), + '1/2/2019', + 'SPACE send to ok should not change date to Jan 2nd' + ); + t.is( + await t.context.session + .findElement(By.css(ex.dialogSelector)) + .getCssValue('display'), + 'none', + 'After sending SPACE to the "cancel" button, the calendar dialog should close' + ); + } +); diff --git a/test/tests/dialog-modal_dialog.js b/test/tests/dialog-modal_dialog.js index 9887df2025..99fda06ec1 100644 --- a/test/tests/dialog-modal_dialog.js +++ b/test/tests/dialog-modal_dialog.js @@ -18,9 +18,12 @@ const ex = { dialogsWithDescribedbySelector: '#dialog2[role="dialog"],#dialog3[role="dialog"],#dialog4[role="dialog"]', dialog1ButtonSelector: '#ex1 button', - dialog2FromDialog1ButtonSelector: '#dialog1 .dialog_form_actions button:nth-child(1)', - dialog3FromDialog1ButtonSelector: '#dialog1 .dialog_form_actions button:nth-child(2)', - dialog4FromDialog2ButtonSelector: '#dialog2 .dialog_form_actions button:nth-child(2)', + dialog2FromDialog1ButtonSelector: + '#dialog1 .dialog_form_actions button:nth-child(1)', + dialog3FromDialog1ButtonSelector: + '#dialog1 .dialog_form_actions button:nth-child(2)', + dialog4FromDialog2ButtonSelector: + '#dialog2 .dialog_form_actions button:nth-child(2)', dialog1FocusableEls: [ '#dialog1 .dialog_form_item:nth-child(1) input', '#dialog1 .dialog_form_item:nth-child(2) input', @@ -29,42 +32,30 @@ const ex = { '#dialog1 .dialog_form_item:nth-child(5) input', '#dialog1 button:nth-child(1)', '#dialog1 button:nth-child(2)', - '#dialog1 button:nth-child(3)' + '#dialog1 button:nth-child(3)', ], dialog2FocusableEls: [ '#dialog2 a', '#dialog2 button:nth-child(2)', - '#dialog2 button:nth-child(3)' + '#dialog2 button:nth-child(3)', ], - dialog3FocusableEls: [ - '#dialog3 button', - '#dialog3 a' - ], - dialog4FocusableEls: [ - '#dialog4 button' - ], - dialog2FirstFocusedEl: '#dialog2_para1' + dialog3FocusableEls: ['#dialog3 button', '#dialog3 a'], + dialog4FocusableEls: ['#dialog4 button'], + dialog2FirstFocusedEl: '#dialog2_para1', }; const openDialog1 = async function (t) { // Click the button to open the address form dialog - await t.context.session - .findElement(By.css(ex.dialog1ButtonSelector)) - .click(); + await t.context.session.findElement(By.css(ex.dialog1ButtonSelector)).click(); // Make sure the appropriate dialog is open const dialog = await t.context.session.findElement(By.css('#dialog1')); - assert( - await dialog.isDisplayed(), - 'dialog1 should have successfully opened' - ); + assert(await dialog.isDisplayed(), 'dialog1 should have successfully opened'); }; const openDialog2 = async function (t) { // Click the button to open the "address form" dialog - await t.context.session - .findElement(By.css(ex.dialog1ButtonSelector)) - .click(); + await t.context.session.findElement(By.css(ex.dialog1ButtonSelector)).click(); // Click the button to open the "verify form" dialog await t.context.session @@ -73,17 +64,12 @@ const openDialog2 = async function (t) { // Make sure the appropriate dialog is open const dialog = await t.context.session.findElement(By.css('#dialog2')); - assert( - await dialog.isDisplayed(), - 'dialog2 should have successfully opened' - ); + assert(await dialog.isDisplayed(), 'dialog2 should have successfully opened'); }; const openDialog3 = async function (t) { // Click the button to open the "address form" dialog - await t.context.session - .findElement(By.css(ex.dialog1ButtonSelector)) - .click(); + await t.context.session.findElement(By.css(ex.dialog1ButtonSelector)).click(); // Click the button to open the "add" dialog await t.context.session @@ -92,17 +78,12 @@ const openDialog3 = async function (t) { // Make sure the appropriate dialog is open const dialog = await t.context.session.findElement(By.css('#dialog3')); - assert( - await dialog.isDisplayed(), - 'dialog3 should have successfully opened' - ); + assert(await dialog.isDisplayed(), 'dialog3 should have successfully opened'); }; const openDialog4 = async function (t) { // Click the button to open the "address form" dialog - await t.context.session - .findElement(By.css(ex.dialog1ButtonSelector)) - .click(); + await t.context.session.findElement(By.css(ex.dialog1ButtonSelector)).click(); // Click the button to open the "verify form" dialog await t.context.session @@ -116,10 +97,7 @@ const openDialog4 = async function (t) { // Make sure the appropriate dialog is open const dialog = await t.context.session.findElement(By.css('#dialog2')); - assert( - await dialog.isDisplayed(), - 'dialog4 should have successfully opened' - ); + assert(await dialog.isDisplayed(), 'dialog4 should have successfully opened'); }; const reload = async (t) => { @@ -129,7 +107,7 @@ const reload = async (t) => { const checkFocus = async function (t, selector) { return t.context.session.wait( t.context.session.executeScript(function () { - const [ selector ] = arguments; + const [selector] = arguments; const items = document.querySelector(selector); return items === document.activeElement; }, selector), @@ -142,12 +120,16 @@ const sendTabToSelector = async function (t, selector) { await el.sendKeys(Key.TAB); // await for focus change before returning - await t.context.session.wait(async function () { - return t.context.session.executeScript(function () { - let selector = arguments[0]; - return document.activeElement !== document.querySelector(selector); - }, selector); - }, t.context.waitTime, 'Timeout waiting for focus to move after TAB sent to: ' + selector); + await t.context.session.wait( + async function () { + return t.context.session.executeScript(function () { + let selector = arguments[0]; + return document.activeElement !== document.querySelector(selector); + }, selector); + }, + t.context.waitTime, + 'Timeout waiting for focus to move after TAB sent to: ' + selector + ); }; const sendShiftTabToSelector = async function (t, selector) { @@ -155,12 +137,16 @@ const sendShiftTabToSelector = async function (t, selector) { await el.sendKeys(Key.chord(Key.SHIFT, Key.TAB)); // await for focus change before returning - await t.context.session.wait(async function () { - return t.context.session.executeScript(function () { - let selector = arguments[0]; - return document.activeElement !== document.querySelector(selector); - }, selector); - }, t.context.waitTime, 'Timeout waiting for focus to move after SHIFT TAB sent to: ' + selector); + await t.context.session.wait( + async function () { + return t.context.session.executeScript(function () { + let selector = arguments[0]; + return document.activeElement !== document.querySelector(selector); + }, selector); + }, + t.context.waitTime, + 'Timeout waiting for focus to move after SHIFT TAB sent to: ' + selector + ); }; const sendEscapeTo = async function (t, selector) { @@ -168,266 +154,334 @@ const sendEscapeTo = async function (t, selector) { await el.sendKeys(Key.ESCAPE); }; - // Attributes -ariaTest('role="dialog" on div element', exampleFile, 'dialog-role', async (t) => { - - - const dialogs = await t.context.queryElements(t, ex.dialogSelector); - - t.is( - dialogs.length, - 4, - 'Four role="dialog" elements should be found by selector: ' + ex.dialogSelector - ); +ariaTest( + 'role="dialog" on div element', + exampleFile, + 'dialog-role', + async (t) => { + const dialogs = await t.context.queryElements(t, ex.dialogSelector); - for (let dialog of dialogs) { t.is( - await dialog.getTagName(), - 'div', - '"role=dialog" should be found on a "div" element' + dialogs.length, + 4, + 'Four role="dialog" elements should be found by selector: ' + + ex.dialogSelector ); + + for (let dialog of dialogs) { + t.is( + await dialog.getTagName(), + 'div', + '"role=dialog" should be found on a "div" element' + ); + } } -}); +); -ariaTest('"aria-labelledby" attribute on role="dialog"', exampleFile, 'aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" attribute on role="dialog"', + exampleFile, + 'aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.dialogSelector); -}); + } +); ariaTest('', exampleFile, 'aria-describedby', async (t) => { - await assertAriaDescribedby(t, ex.dialogsWithDescribedbySelector); + await assertAriaDescribedby(t, ex.dialogsWithDescribedbySelector); }); -ariaTest('"aria-modal" attribute on role="dialog"', exampleFile, 'aria-modal', async (t) => { +ariaTest( + '"aria-modal" attribute on role="dialog"', + exampleFile, + 'aria-modal', + async (t) => { await assertAttributeValues(t, ex.dialogSelector, 'aria-modal', 'true'); -}); - + } +); // Keys -ariaTest('tab changes focus within dialog', exampleFile, 'key-tab', async (t) => { - - /* DIALOG 1 */ +ariaTest( + 'tab changes focus within dialog', + exampleFile, + 'key-tab', + async (t) => { + /* DIALOG 1 */ - await openDialog1(t); + await openDialog1(t); - // Loop through the focusable elements (focus is on first focusable element on popup) - for (let i = 0; i < ex.dialog1FocusableEls.length; i++) { + // Loop through the focusable elements (focus is on first focusable element on popup) + for (let i = 0; i < ex.dialog1FocusableEls.length; i++) { + t.true( + await checkFocus(t, ex.dialog1FocusableEls[i]), + 'Focus should be on "' + + ex.dialog1FocusableEls[i] + + '" after ' + + i + + ' tabs have been sent to dialog 1' + ); + + await sendTabToSelector(t, ex.dialog1FocusableEls[i]); + } + + // Check that the focus returns to the first focusable element + let totaltabs = ex.dialog1FocusableEls.length; t.true( - await checkFocus(t, ex.dialog1FocusableEls[i]), - 'Focus should be on "' + ex.dialog1FocusableEls[i] + '" after ' + - i + ' tabs have been sent to dialog 1' + await checkFocus(t, ex.dialog1FocusableEls[0]), + 'Focus should be on "' + + ex.dialog1FocusableEls[0] + + '" after ' + + totaltabs + + ' tabs have been sent to dialog 1' ); - await sendTabToSelector(t, ex.dialog1FocusableEls[i]); - } - - // Check that the focus returns to the first focusable element - let totaltabs = ex.dialog1FocusableEls.length; - t.true( - await checkFocus(t, ex.dialog1FocusableEls[0]), - 'Focus should be on "' + ex.dialog1FocusableEls[0] + '" after ' + - totaltabs + ' tabs have been sent to dialog 1' - ); + /* DIALOG 2 */ - /* DIALOG 2 */ - - await reload(t); - await openDialog2(t); - - // Loop through the focusable elements - // focus is not first focusable element on popup -- send tab to start - await sendTabToSelector(t, ex.dialog2FirstFocusedEl); + await reload(t); + await openDialog2(t); - for (let i = 0; i < ex.dialog2FocusableEls.length; i++) { + // Loop through the focusable elements + // focus is not first focusable element on popup -- send tab to start + await sendTabToSelector(t, ex.dialog2FirstFocusedEl); + + for (let i = 0; i < ex.dialog2FocusableEls.length; i++) { + t.true( + await checkFocus(t, ex.dialog2FocusableEls[i]), + 'Focus should be on "' + + ex.dialog2FocusableEls[i] + + '" after ' + + (i + 1) + + ' tabs have been sent to dialog 2' + ); + + await sendTabToSelector(t, ex.dialog2FocusableEls[i]); + } + + // Check that the focus returns to the first focusable element + totaltabs = ex.dialog2FocusableEls.length + 1; t.true( - await checkFocus(t, ex.dialog2FocusableEls[i]), - 'Focus should be on "' + ex.dialog2FocusableEls[i] + '" after ' + - (i + 1) + ' tabs have been sent to dialog 2' + await checkFocus(t, ex.dialog2FocusableEls[0]), + 'Focus should be on "' + + ex.dialog2FocusableEls[0] + + '" after ' + + totaltabs + + ' tabs have been sent to dialog 1' ); - await sendTabToSelector(t, ex.dialog2FocusableEls[i]); - } + /* DIALOG 3 */ - // Check that the focus returns to the first focusable element - totaltabs = ex.dialog2FocusableEls.length + 1; - t.true( - await checkFocus(t, ex.dialog2FocusableEls[0]), - 'Focus should be on "' + ex.dialog2FocusableEls[0] + '" after ' + - totaltabs + ' tabs have been sent to dialog 1' - ); - - /* DIALOG 3 */ - - await reload(t); - await openDialog3(t); + await reload(t); + await openDialog3(t); - // Loop through the focusable elements - for (let i = 0; i < ex.dialog3FocusableEls.length; i++) { + // Loop through the focusable elements + for (let i = 0; i < ex.dialog3FocusableEls.length; i++) { + t.true( + await checkFocus(t, ex.dialog3FocusableEls[i]), + 'Focus should be on item "' + + ex.dialog3FocusableEls[i] + + '" after ' + + (i + 1) + + ' tabs have been sent to dialog 3' + ); + + await sendTabToSelector(t, ex.dialog3FocusableEls[i]); + } + + // Check that the focus returns to the first focusable element + totaltabs = ex.dialog3FocusableEls.length + 1; t.true( - await checkFocus(t, ex.dialog3FocusableEls[i]), - 'Focus should be on item "' + ex.dialog3FocusableEls[i] + '" after ' + - (i + 1) + ' tabs have been sent to dialog 3' + await checkFocus(t, ex.dialog3FocusableEls[0]), + 'Focus should be on "' + + ex.dialog3FocusableEls[0] + + '" after ' + + totaltabs + + ' tabs have been sent to dialog 3' ); - await sendTabToSelector(t, ex.dialog3FocusableEls[i]); - } - - // Check that the focus returns to the first focusable element - totaltabs = ex.dialog3FocusableEls.length + 1; - t.true( - await checkFocus(t, ex.dialog3FocusableEls[0]), - 'Focus should be on "' + ex.dialog3FocusableEls[0] + '" after ' + - totaltabs + ' tabs have been sent to dialog 3' - ); - - /* DIALOG 4 */ - - await reload(t); - await openDialog4(t); - - // There is only one button on dialog 4 - t.true( - await checkFocus(t, ex.dialog4FocusableEls[0]), - 'Focus should be on item "' + ex.dialog4FocusableEls[0] + '" when dialog 4 is opened' - ); + /* DIALOG 4 */ - // Make focus does not change - let el = await t.context.session.findElement(By.css(ex.dialog4FocusableEls[0])); - await el.sendKeys(Key.TAB); - t.true( - await checkFocus(t, ex.dialog4FocusableEls[0]), - 'Focus should remain on: "' + ex.dialog4FocusableEls[0] + '" after tabs in dialog 4' - ); - -}); - -ariaTest('shift tab changes focus within dialog', exampleFile, 'key-shift-tab', async (t) => { - - /* DIALOG 1 */ - - await openDialog1(t); - - // Loop through the focusable elements backwards (focus is on first focusable element on popup) - await sendShiftTabToSelector(t, ex.dialog1FocusableEls[0]); - let shifttabcount = 1; + await reload(t); + await openDialog4(t); - for (let i = ex.dialog1FocusableEls.length - 1; i >= 0; i--) { + // There is only one button on dialog 4 t.true( - await checkFocus(t, ex.dialog1FocusableEls[i]), - 'Focus should be on item "' + ex.dialog1FocusableEls[i] + '" after ' + shifttabcount + - ' shift tabs have been sent to dialog 1' + await checkFocus(t, ex.dialog4FocusableEls[0]), + 'Focus should be on item "' + + ex.dialog4FocusableEls[0] + + '" when dialog 4 is opened' ); - await sendShiftTabToSelector(t, ex.dialog1FocusableEls[i]); - shifttabcount++; + // Make focus does not change + let el = await t.context.session.findElement( + By.css(ex.dialog4FocusableEls[0]) + ); + await el.sendKeys(Key.TAB); + t.true( + await checkFocus(t, ex.dialog4FocusableEls[0]), + 'Focus should remain on: "' + + ex.dialog4FocusableEls[0] + + '" after tabs in dialog 4' + ); } +); - // Check that the focus returns to the last focusable element - let totaltabs = ex.dialog1FocusableEls.length + 1; - let lastindex = ex.dialog1FocusableEls.length - 1; - t.true( - await checkFocus(t, ex.dialog1FocusableEls[lastindex]), - 'Focus should be on "' + ex.dialog1FocusableEls[lastindex] + '" after ' + - totaltabs + ' tabs have been sent to dialog 1' - ); +ariaTest( + 'shift tab changes focus within dialog', + exampleFile, + 'key-shift-tab', + async (t) => { + /* DIALOG 1 */ - /* DIALOG 2 */ + await openDialog1(t); - await reload(t); - await openDialog2(t); + // Loop through the focusable elements backwards (focus is on first focusable element on popup) + await sendShiftTabToSelector(t, ex.dialog1FocusableEls[0]); + let shifttabcount = 1; + + for (let i = ex.dialog1FocusableEls.length - 1; i >= 0; i--) { + t.true( + await checkFocus(t, ex.dialog1FocusableEls[i]), + 'Focus should be on item "' + + ex.dialog1FocusableEls[i] + + '" after ' + + shifttabcount + + ' shift tabs have been sent to dialog 1' + ); + + await sendShiftTabToSelector(t, ex.dialog1FocusableEls[i]); + shifttabcount++; + } + + // Check that the focus returns to the last focusable element + let totaltabs = ex.dialog1FocusableEls.length + 1; + let lastindex = ex.dialog1FocusableEls.length - 1; + t.true( + await checkFocus(t, ex.dialog1FocusableEls[lastindex]), + 'Focus should be on "' + + ex.dialog1FocusableEls[lastindex] + + '" after ' + + totaltabs + + ' tabs have been sent to dialog 1' + ); - // Set up: - // First, focus will be on a div, send SHIFT+TAB - await sendShiftTabToSelector(t, ex.dialog2FirstFocusedEl); - // Second, focus will be on the first focusable element second, send SHIFT+TAB - await sendShiftTabToSelector(t, ex.dialog2FocusableEls[0]); + /* DIALOG 2 */ - shifttabcount = 2; + await reload(t); + await openDialog2(t); - // Loop through all focusable elements backward - for (let i = ex.dialog2FocusableEls.length - 1; i >= 0; i--) { + // Set up: + // First, focus will be on a div, send SHIFT+TAB + await sendShiftTabToSelector(t, ex.dialog2FirstFocusedEl); + // Second, focus will be on the first focusable element second, send SHIFT+TAB + await sendShiftTabToSelector(t, ex.dialog2FocusableEls[0]); + + shifttabcount = 2; + + // Loop through all focusable elements backward + for (let i = ex.dialog2FocusableEls.length - 1; i >= 0; i--) { + t.true( + await checkFocus(t, ex.dialog2FocusableEls[i]), + 'Focus should be on item "' + + ex.dialog2FocusableEls[i] + + '" after ' + + shifttabcount + + ' shift tabs have been sent to dialog 2' + ); + await sendShiftTabToSelector(t, ex.dialog2FocusableEls[i]); + shifttabcount++; + } + + // Check that the focus returns to the last focusable element + totaltabs = ex.dialog2FocusableEls.length + 2; + lastindex = ex.dialog2FocusableEls.length - 1; t.true( - await checkFocus(t, ex.dialog2FocusableEls[i]), - 'Focus should be on item "' + ex.dialog2FocusableEls[i] + '" after ' + shifttabcount + - ' shift tabs have been sent to dialog 2' + await checkFocus(t, ex.dialog2FocusableEls[lastindex]), + 'Focus should be on "' + + ex.dialog2FocusableEls[lastindex] + + '" after ' + + totaltabs + + ' tabs have been sent to dialog 1' ); - await sendShiftTabToSelector(t, ex.dialog2FocusableEls[i]); - shifttabcount++; - } - // Check that the focus returns to the last focusable element - totaltabs = ex.dialog2FocusableEls.length + 2; - lastindex = ex.dialog2FocusableEls.length - 1; - t.true( - await checkFocus(t, ex.dialog2FocusableEls[lastindex]), - 'Focus should be on "' + ex.dialog2FocusableEls[lastindex] + '" after ' + - totaltabs + ' tabs have been sent to dialog 1' - ); + /* DIALOG 3 */ + await reload(t); + await openDialog3(t); - /* DIALOG 3 */ + // Loop through the focusable elements backwards (focus is on first focusable element on popup) + await sendShiftTabToSelector(t, ex.dialog3FocusableEls[0]); + shifttabcount = 1; + + for (let i = ex.dialog3FocusableEls.length - 1; i >= 0; i--) { + t.true( + await checkFocus(t, ex.dialog3FocusableEls[i]), + 'Focus should be on item "' + + ex.dialog3FocusableEls[i] + + '" after ' + + shifttabcount + + ' shift tabs have been sent to dialog 3' + ); + + await sendShiftTabToSelector(t, ex.dialog3FocusableEls[i]); + shifttabcount++; + } + + // Check that the focus returns to the first focusable element + totaltabs = ex.dialog3FocusableEls.length + 1; + lastindex = ex.dialog3FocusableEls.length - 1; + t.true( + await checkFocus(t, ex.dialog3FocusableEls[lastindex]), + 'Focus should be on "' + + ex.dialog3FocusableEls[lastindex] + + '" after ' + + totaltabs + + ' tabs have been sent to dialog 3' + ); - await reload(t); - await openDialog3(t); + /* DIALOG 4 */ - // Loop through the focusable elements backwards (focus is on first focusable element on popup) - await sendShiftTabToSelector(t, ex.dialog3FocusableEls[0]); - shifttabcount = 1; + await reload(t); + await openDialog4(t); - for (let i = ex.dialog3FocusableEls.length - 1; i >= 0; i--) { + // There is only one button on dialog 4 t.true( - await checkFocus(t, ex.dialog3FocusableEls[i]), - 'Focus should be on item "' + ex.dialog3FocusableEls[i] + '" after ' + shifttabcount + - ' shift tabs have been sent to dialog 3' + await checkFocus(t, ex.dialog4FocusableEls[0]), + 'Focus should be on item "' + + ex.dialog4FocusableEls[0] + + '" when dialog 4 is opened' ); - await sendShiftTabToSelector(t, ex.dialog3FocusableEls[i]); - shifttabcount++; + // Make focus does not change + let el = await t.context.session.findElement( + By.css(ex.dialog4FocusableEls[0]) + ); + await el.sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + t.true( + await checkFocus(t, ex.dialog4FocusableEls[0]), + 'Focus should remain on: "' + + ex.dialog4FocusableEls[0] + + '" after tabs in dialog 4' + ); } - - // Check that the focus returns to the first focusable element - totaltabs = ex.dialog3FocusableEls.length + 1; - lastindex = ex.dialog3FocusableEls.length - 1; - t.true( - await checkFocus(t, ex.dialog3FocusableEls[lastindex]), - 'Focus should be on "' + ex.dialog3FocusableEls[lastindex] + '" after ' + - totaltabs + ' tabs have been sent to dialog 3' - ); - - /* DIALOG 4 */ - - await reload(t); - await openDialog4(t); - - // There is only one button on dialog 4 - t.true( - await checkFocus(t, ex.dialog4FocusableEls[0]), - 'Focus should be on item "' + ex.dialog4FocusableEls[0] + '" when dialog 4 is opened' - ); - - // Make focus does not change - let el = await t.context.session.findElement(By.css(ex.dialog4FocusableEls[0])); - await el.sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - t.true( - await checkFocus(t, ex.dialog4FocusableEls[0]), - 'Focus should remain on: "' + ex.dialog4FocusableEls[0] + '" after tabs in dialog 4' - ); -}); +); ariaTest('escape closes dialog', exampleFile, 'key-escape', async (t) => { - /* DIALOG 1 */ for (let selector of ex.dialog1FocusableEls) { await openDialog1(t); await sendEscapeTo(t, selector); - const modalEl = await t.context.session.findElement(By.css(ex.dialog1Selector)); + const modalEl = await t.context.session.findElement( + By.css(ex.dialog1Selector) + ); t.false( await modalEl.isDisplayed(), - 'Modal 1 should not be displayed after sending escape to element: ' + selector + 'Modal 1 should not be displayed after sending escape to element: ' + + selector ); await reload(t); @@ -439,10 +493,13 @@ ariaTest('escape closes dialog', exampleFile, 'key-escape', async (t) => { await openDialog2(t); await sendEscapeTo(t, selector); - const modalEl = await t.context.session.findElement(By.css(ex.dialog2Selector)); + const modalEl = await t.context.session.findElement( + By.css(ex.dialog2Selector) + ); t.false( await modalEl.isDisplayed(), - 'Modal 2 should not be displayed after sending escape to element: ' + selector + 'Modal 2 should not be displayed after sending escape to element: ' + + selector ); await reload(t); @@ -454,10 +511,13 @@ ariaTest('escape closes dialog', exampleFile, 'key-escape', async (t) => { await openDialog3(t); await sendEscapeTo(t, selector); - const modalEl = await t.context.session.findElement(By.css(ex.dialog3Selector)); + const modalEl = await t.context.session.findElement( + By.css(ex.dialog3Selector) + ); t.false( await modalEl.isDisplayed(), - 'Modal 3 should not be displayed after sending escape to element: ' + selector + 'Modal 3 should not be displayed after sending escape to element: ' + + selector ); await reload(t); @@ -468,10 +528,12 @@ ariaTest('escape closes dialog', exampleFile, 'key-escape', async (t) => { await openDialog4(t); await sendEscapeTo(t, ex.dialog4FocusableEls[0]); - const modalEl = await t.context.session.findElement(By.css(ex.dialog4Selector)); + const modalEl = await t.context.session.findElement( + By.css(ex.dialog4Selector) + ); t.false( await modalEl.isDisplayed(), - 'Modal 4 should not be displayed after sending escape to element: ' + ex.dialog4FocusableEls[0] + 'Modal 4 should not be displayed after sending escape to element: ' + + ex.dialog4FocusableEls[0] ); - }); diff --git a/test/tests/disclosure_faq.js b/test/tests/disclosure_faq.js index 92cd29c73d..1cfc71fa4a 100644 --- a/test/tests/disclosure_faq.js +++ b/test/tests/disclosure_faq.js @@ -15,62 +15,78 @@ const ex = { '#ex1 dt:nth-of-type(1) button', '#ex1 dt:nth-of-type(2) button', '#ex1 dt:nth-of-type(3) button', - '#ex1 dt:nth-of-type(4) button' + '#ex1 dt:nth-of-type(4) button', ], answerSelectors: [ '#ex1 dd:nth-of-type(1)', '#ex1 dd:nth-of-type(2)', '#ex1 dd:nth-of-type(3)', - '#ex1 dd:nth-of-type(4)' - ] + '#ex1 dd:nth-of-type(4)', + ], }; - const waitAndCheckExpandedTrue = async function (t, selector) { - return t.context.session.wait(async function () { - const element = t.context.session.findElement(By.css(selector)); - return (await element.getAttribute('aria-expanded')) === 'true'; - }, t.context.waitTime, 'Timeout waiting for aria-expanded to change to true on element: ' + selector); + return t.context.session.wait( + async function () { + const element = t.context.session.findElement(By.css(selector)); + return (await element.getAttribute('aria-expanded')) === 'true'; + }, + t.context.waitTime, + 'Timeout waiting for aria-expanded to change to true on element: ' + + selector + ); }; const waitAndCheckExpandedFalse = async function (t, selector) { - return t.context.session.wait(async function () { - const element = t.context.session.findElement(By.css(selector)); - return (await element.getAttribute('aria-expanded')) === 'false'; - }, t.context.waitTime, 'Timeout waiting for aria-expanded to change to false on element: ' + selector); + return t.context.session.wait( + async function () { + const element = t.context.session.findElement(By.css(selector)); + return (await element.getAttribute('aria-expanded')) === 'false'; + }, + t.context.waitTime, + 'Timeout waiting for aria-expanded to change to false on element: ' + + selector + ); }; - // Attributes -ariaTest('"aria-controls" attribute on button', exampleFile, 'button-aria-controls', async (t) => { +ariaTest( + '"aria-controls" attribute on button', + exampleFile, + 'button-aria-controls', + async (t) => { await assertAriaControls(t, ex.buttonSelector); -}); - -ariaTest('"aria-expanded" attribute on button', exampleFile, 'button-aria-expanded', async (t) => { - - await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'false'); - - let buttons = await t.context.queryElements(t, ex.buttonSelector); - for (let button of buttons) { - await button.click(); } - - let answers = await t.context.queryElements(t, ex.buttonSelector); - for (let answer of answers) { - t.true( - await answer.isDisplayed(), - 'All answers should de displayed after clicking all questions' - ); +); + +ariaTest( + '"aria-expanded" attribute on button', + exampleFile, + 'button-aria-expanded', + async (t) => { + await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'false'); + + let buttons = await t.context.queryElements(t, ex.buttonSelector); + for (let button of buttons) { + await button.click(); + } + + let answers = await t.context.queryElements(t, ex.buttonSelector); + for (let answer of answers) { + t.true( + await answer.isDisplayed(), + 'All answers should de displayed after clicking all questions' + ); + } + + await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'true'); } - - await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'true'); -}); +); // Keys ariaTest('TAB should move focus', exampleFile, 'key-tab', async (t) => { - await assertTabOrder(t, ex.buttonSelectors); let buttons = await t.context.queryElements(t, ex.buttonSelector); @@ -81,68 +97,92 @@ ariaTest('TAB should move focus', exampleFile, 'key-tab', async (t) => { await assertTabOrder(t, ex.buttonSelectors); }); -ariaTest('key ENTER expands details', exampleFile, 'key-enter-or-space', async (t) => { - - for (let index = 0; index < ex.buttonSelectors.length; index++) { - let buttonSelector = ex.buttonSelectors[index]; - let answerSelector = ex.answerSelectors[index]; - let button = await t.context.session.findElement(By.css(buttonSelector)); - - await button.sendKeys(Key.ENTER); - - t.true( - await waitAndCheckExpandedTrue(t, buttonSelector), - 'Question should have aria-expanded true after sending ENTER: ' + buttonSelector - ); - - t.true( - await t.context.session.findElement(By.css(answerSelector)).isDisplayed(), - 'Answer should be displayed after sending ENTER to button: ' + buttonSelector - ); - - await button.sendKeys(Key.ENTER); - - t.true( - await waitAndCheckExpandedFalse(t, buttonSelector), - 'Question should have aria-expanded false after sending ENTER twice: ' + buttonSelector - ); - - t.true( - await t.context.session.findElement(By.css(answerSelector)).isDisplayed(), - 'Answer should not be displayed after sending ENTER twice to button: ' + buttonSelector - ); +ariaTest( + 'key ENTER expands details', + exampleFile, + 'key-enter-or-space', + async (t) => { + for (let index = 0; index < ex.buttonSelectors.length; index++) { + let buttonSelector = ex.buttonSelectors[index]; + let answerSelector = ex.answerSelectors[index]; + let button = await t.context.session.findElement(By.css(buttonSelector)); + + await button.sendKeys(Key.ENTER); + + t.true( + await waitAndCheckExpandedTrue(t, buttonSelector), + 'Question should have aria-expanded true after sending ENTER: ' + + buttonSelector + ); + + t.true( + await t.context.session + .findElement(By.css(answerSelector)) + .isDisplayed(), + 'Answer should be displayed after sending ENTER to button: ' + + buttonSelector + ); + + await button.sendKeys(Key.ENTER); + + t.true( + await waitAndCheckExpandedFalse(t, buttonSelector), + 'Question should have aria-expanded false after sending ENTER twice: ' + + buttonSelector + ); + + t.true( + await t.context.session + .findElement(By.css(answerSelector)) + .isDisplayed(), + 'Answer should not be displayed after sending ENTER twice to button: ' + + buttonSelector + ); + } } -}); - -ariaTest('key SPACE expands details', exampleFile, 'key-enter-or-space', async (t) => { - - for (let index = 0; index < ex.buttonSelectors.length; index++) { - let buttonSelector = ex.buttonSelectors[index]; - let answerSelector = ex.answerSelectors[index]; - let button = await t.context.session.findElement(By.css(buttonSelector)); - - await button.sendKeys(Key.SPACE); - - t.true( - await waitAndCheckExpandedTrue(t, buttonSelector), - 'Question should have aria-expanded true after sending SPACE: ' + buttonSelector - ); - - t.true( - await t.context.session.findElement(By.css(answerSelector)).isDisplayed(), - 'Answer should be displayed after sending SPACE to button: ' + buttonSelector - ); - - await button.sendKeys(Key.SPACE); - - t.true( - await waitAndCheckExpandedFalse(t, buttonSelector), - 'Question should have aria-expanded false after sending SPACE twice: ' + buttonSelector - ); - - t.true( - await t.context.session.findElement(By.css(answerSelector)).isDisplayed(), - 'Answer should not be displayed after sending SPACE twice to button: ' + buttonSelector - ); +); + +ariaTest( + 'key SPACE expands details', + exampleFile, + 'key-enter-or-space', + async (t) => { + for (let index = 0; index < ex.buttonSelectors.length; index++) { + let buttonSelector = ex.buttonSelectors[index]; + let answerSelector = ex.answerSelectors[index]; + let button = await t.context.session.findElement(By.css(buttonSelector)); + + await button.sendKeys(Key.SPACE); + + t.true( + await waitAndCheckExpandedTrue(t, buttonSelector), + 'Question should have aria-expanded true after sending SPACE: ' + + buttonSelector + ); + + t.true( + await t.context.session + .findElement(By.css(answerSelector)) + .isDisplayed(), + 'Answer should be displayed after sending SPACE to button: ' + + buttonSelector + ); + + await button.sendKeys(Key.SPACE); + + t.true( + await waitAndCheckExpandedFalse(t, buttonSelector), + 'Question should have aria-expanded false after sending SPACE twice: ' + + buttonSelector + ); + + t.true( + await t.context.session + .findElement(By.css(answerSelector)) + .isDisplayed(), + 'Answer should not be displayed after sending SPACE twice to button: ' + + buttonSelector + ); + } } -}); +); diff --git a/test/tests/disclosure_img-long-description.js b/test/tests/disclosure_img-long-description.js index 322fb4d774..d801adb7ad 100644 --- a/test/tests/disclosure_img-long-description.js +++ b/test/tests/disclosure_img-long-description.js @@ -9,123 +9,170 @@ const exampleFile = 'disclosure/disclosure-img-long-description.html'; const ex = { buttonSelector: '#ex1 button', - descriptionSelector: '#ex1 #id_long_desc' + descriptionSelector: '#ex1 #id_long_desc', }; // Attributes -ariaTest('"aria-controls" attribute on button', exampleFile, 'aria-controls', async (t) => { +ariaTest( + '"aria-controls" attribute on button', + exampleFile, + 'aria-controls', + async (t) => { await assertAriaControls(t, ex.buttonSelector); -}); - -ariaTest('"aria-expanded" attribute on button', exampleFile, 'aria-expanded', async (t) => { - - await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'false'); - - let description = await t.context.session.findElement(By.css(ex.buttonSelector)); - t.true( - await description.isDisplayed(), - 'Description should not be displayed if button has aria-expanded="false"' - ); - - let button = await t.context.session.findElement(By.css(ex.buttonSelector)); - await button.click(); - - await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'true'); - t.true( - await description.isDisplayed(), - 'Description should be displayed if button has aria-expanded="true"' - ); - -}); + } +); + +ariaTest( + '"aria-expanded" attribute on button', + exampleFile, + 'aria-expanded', + async (t) => { + await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'false'); + + let description = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); + t.true( + await description.isDisplayed(), + 'Description should not be displayed if button has aria-expanded="false"' + ); + + let button = await t.context.session.findElement(By.css(ex.buttonSelector)); + await button.click(); + + await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'true'); + t.true( + await description.isDisplayed(), + 'Description should be displayed if button has aria-expanded="true"' + ); + } +); // Keys -ariaTest('TAB should move focus to button', exampleFile, 'key-tab', async (t) => { - - // Send SHIFT+TAB to button - await await t.context.session.findElement(By.css(ex.buttonSelector)) - .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); - - // Find the element that is in focus - const previousElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - - // Send that element TAB - await previousElement.sendKeys(Key.TAB); - - // Confirm focus is on the button - const focusIsOnButton = await t.context.session.executeScript(function () { - const selector = arguments[0]; - const items = document.querySelector(selector); - return items === document.activeElement; - }, ex.buttonSelector); - - t.true( - focusIsOnButton, - 'The disclosure button (' + ex.buttonSelector + ') should be reachable in tab sequence.' - ); -}); - -ariaTest('key ENTER expands details', exampleFile, 'key-space-or-enter', async (t) => { - - const button = await t.context.session.findElement(By.css(ex.buttonSelector)); - const description = await t.context.session.findElement(By.css(ex.descriptionSelector)); - await button.sendKeys(Key.ENTER); - - t.is( - await button.getAttribute('aria-expanded'), - 'true', - 'Button should have aria-expanded true after sending ENTER to "' + ex.buttonSelector + '"' - ); - - t.true( - await description.isDisplayed(), - 'Description should be displayed after sending ENTER to button to "' + ex.buttonSelector + '"' - ); - - await button.sendKeys(Key.ENTER); - - t.is( - await button.getAttribute('aria-expanded'), - 'false', - 'Button should have aria-expanded false after sending ENTER twice to "' + ex.buttonSelector + '"' - ); - - t.false( - await description.isDisplayed(), - 'Description should not be displayed after sending ENTER twice to button to "' + ex.buttonSelector + '"' - ); -}); - -ariaTest('key SPACE expands details', exampleFile, 'key-space-or-enter', async (t) => { - - let button = await t.context.session.findElement(By.css(ex.buttonSelector)); - let description = await t.context.session.findElement(By.css(ex.descriptionSelector)); - await button.sendKeys(Key.SPACE); - - t.is( - await button.getAttribute('aria-expanded'), - 'true', - 'Button should have aria-expanded true after sending SPACE to "' + ex.buttonSelector + '"' - ); - - t.true( - await description.isDisplayed(), - 'Description should be displayed after sending SPACE to button to "' + ex.buttonSelector + '"' - ); - - await button.sendKeys(Key.SPACE); - - t.is( - await button.getAttribute('aria-expanded'), - 'false', - 'Button should have aria-expanded false after sending SPACE twice to "' + ex.buttonSelector + '"' - ); - - t.false( - await description.isDisplayed(), - 'Description should not be displayed after sending SPACE twice to button to "' + ex.buttonSelector + '"' - ); -}); +ariaTest( + 'TAB should move focus to button', + exampleFile, + 'key-tab', + async (t) => { + // Send SHIFT+TAB to button + await await t.context.session + .findElement(By.css(ex.buttonSelector)) + .sendKeys(Key.chord(Key.SHIFT, Key.TAB)); + + // Find the element that is in focus + const previousElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); + + // Send that element TAB + await previousElement.sendKeys(Key.TAB); + + // Confirm focus is on the button + const focusIsOnButton = await t.context.session.executeScript(function () { + const selector = arguments[0]; + const items = document.querySelector(selector); + return items === document.activeElement; + }, ex.buttonSelector); + + t.true( + focusIsOnButton, + 'The disclosure button (' + + ex.buttonSelector + + ') should be reachable in tab sequence.' + ); + } +); + +ariaTest( + 'key ENTER expands details', + exampleFile, + 'key-space-or-enter', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); + const description = await t.context.session.findElement( + By.css(ex.descriptionSelector) + ); + await button.sendKeys(Key.ENTER); + + t.is( + await button.getAttribute('aria-expanded'), + 'true', + 'Button should have aria-expanded true after sending ENTER to "' + + ex.buttonSelector + + '"' + ); + + t.true( + await description.isDisplayed(), + 'Description should be displayed after sending ENTER to button to "' + + ex.buttonSelector + + '"' + ); + + await button.sendKeys(Key.ENTER); + + t.is( + await button.getAttribute('aria-expanded'), + 'false', + 'Button should have aria-expanded false after sending ENTER twice to "' + + ex.buttonSelector + + '"' + ); + + t.false( + await description.isDisplayed(), + 'Description should not be displayed after sending ENTER twice to button to "' + + ex.buttonSelector + + '"' + ); + } +); + +ariaTest( + 'key SPACE expands details', + exampleFile, + 'key-space-or-enter', + async (t) => { + let button = await t.context.session.findElement(By.css(ex.buttonSelector)); + let description = await t.context.session.findElement( + By.css(ex.descriptionSelector) + ); + await button.sendKeys(Key.SPACE); + + t.is( + await button.getAttribute('aria-expanded'), + 'true', + 'Button should have aria-expanded true after sending SPACE to "' + + ex.buttonSelector + + '"' + ); + + t.true( + await description.isDisplayed(), + 'Description should be displayed after sending SPACE to button to "' + + ex.buttonSelector + + '"' + ); + + await button.sendKeys(Key.SPACE); + + t.is( + await button.getAttribute('aria-expanded'), + 'false', + 'Button should have aria-expanded false after sending SPACE twice to "' + + ex.buttonSelector + + '"' + ); + + t.false( + await description.isDisplayed(), + 'Description should not be displayed after sending SPACE twice to button to "' + + ex.buttonSelector + + '"' + ); + } +); diff --git a/test/tests/disclosure_navigation.js b/test/tests/disclosure_navigation.js index 1428b377dd..7376adca87 100644 --- a/test/tests/disclosure_navigation.js +++ b/test/tests/disclosure_navigation.js @@ -1,250 +1,430 @@ -'use strict'; - -const { ariaTest } = require('..'); -const { By, Key } = require('selenium-webdriver'); -const assertAriaControls = require('../util/assertAriaControls'); -const assertAttributeValues = require('../util/assertAttributeValues'); -const assertTabOrder = require('../util/assertTabOrder'); -const assertHasFocus = require('../util/assertHasFocus'); - -const exampleFile = 'disclosure/disclosure-navigation.html'; - -const ex = { - buttonSelector: '#exTest button', - menuSelector: '#exTest > li > ul', - linkSelector: '#exTest > li > ul a', - buttonSelectors: [ - '#exTest > li:nth-child(1) button', - '#exTest > li:nth-child(2) button', - '#exTest > li:nth-child(3) button' - ], - menuSelectors: [ - '#exTest > li:nth-child(1) ul', - '#exTest > li:nth-child(2) ul', - '#exTest > li:nth-child(3) ul' - ] -}; - -// Attributes - -ariaTest('"aria-controls" attribute on button', exampleFile, 'button-aria-controls', async (t) => { - await assertAriaControls(t, ex.buttonSelector); -}); - -ariaTest('"aria-expanded" attribute on button', exampleFile, 'button-aria-expanded', async (t) => { - - await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'false'); - - let buttons = await t.context.queryElements(t, ex.buttonSelector); - let menus = await t.context.queryElements(t, ex.menuSelector); - for (let i = buttons.length - 1; i >= 0; i--) { - await buttons[i].click(); - t.true( - await menus[i].isDisplayed(), - 'Each dropdown menu should display after clicking its trigger' - ); - await assertAttributeValues(t, ex.buttonSelectors[i], 'aria-expanded', 'true'); - } -}); - -ariaTest('"aria-current" attribute on links', exampleFile, 'link-aria-current', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - for (let b = 0; b < buttons.length; b++) { - const links = await t.context.queryElements(t, 'a', menus[b]); - - for (let l = 0; l < links.length; l++) { - - await buttons[b].click(); - await links[l].click(); - - t.is( - await links[l].getAttribute('aria-current'), - 'page', - 'after clicking link at index ' + l + ' on menu ' + b + 'aria-current should be set to page' - ); - - let ariaCurrentLinks = await t.context.queryElements(t, `${ex.linkSelector}[aria-current="page"]`); - - t.is( - ariaCurrentLinks.length, - 1, - 'after clicking link at index ' + l + ' on menu ' + b + ', only one link should have aria-current set' - ); - } - } - -}); - -// Keys - -ariaTest('TAB should move focus between buttons', exampleFile, 'key-tab', async (t) => { - - await assertTabOrder(t, ex.buttonSelectors); -}); - -ariaTest('key ENTER expands dropdown', exampleFile, 'key-enter-space', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - for (let i = buttons.length - 1; i >= 0; i--) { - await buttons[i].sendKeys(Key.ENTER); - await assertAttributeValues(t, ex.buttonSelectors[i], 'aria-expanded', 'true'); - t.true( - await menus[i].isDisplayed(), - 'Dropdown menu should display sending ENTER to its trigger' - ); - - await buttons[i].sendKeys(Key.ENTER); - await assertAttributeValues(t, ex.buttonSelectors[i], 'aria-expanded', 'false'); - t.false( - await menus[i].isDisplayed(), - 'Dropdown menu should close after sending ENTER twice to its trigger' - ); - } -}); - -ariaTest('key SPACE expands dropdown', exampleFile, 'key-enter-space', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - for (let i = buttons.length - 1; i >= 0; i--) { - await buttons[i].sendKeys(Key.SPACE); - await assertAttributeValues(t, ex.buttonSelectors[i], 'aria-expanded', 'true'); - t.true( - await menus[i].isDisplayed(), - 'Dropdown menu should display sending SPACE to its trigger' - ); - - await buttons[i].sendKeys(Key.SPACE); - await assertAttributeValues(t, ex.buttonSelectors[i], 'aria-expanded', 'false'); - t.false( - await menus[i].isDisplayed(), - 'Dropdown menu should close after sending SPACE twice to its trigger' - ); - } -}); - -ariaTest('key ESCAPE closes dropdown', exampleFile, 'key-escape', async (t) => { - - const button = await t.context.session.findElement(By.css(ex.buttonSelectors[0])); - const menu = await t.context.session.findElement(By.css(ex.menuSelectors[0])); - const firstLink = await t.context.session.findElement(By.css(`${ex.menuSelectors[0]} a`)); - - await button.click(); - t.true( - await menu.isDisplayed(), - 'Dropdown menu is displayed on click' - ); - - await firstLink.sendKeys(Key.ESCAPE); - await assertAttributeValues(t, ex.buttonSelectors[0], 'aria-expanded', 'false'); - t.false( - await menu.isDisplayed(), - 'Dropdown menu should close after sending ESCAPE to the menu' - ); -}); - -ariaTest('arrow keys move focus between disclosure buttons', exampleFile, 'key-arrows', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - await buttons[0].sendKeys(Key.ARROW_RIGHT); - await assertHasFocus(t, ex.buttonSelectors[1], 'right arrow moves focus from first to second button'); - - await buttons[0].sendKeys(Key.ARROW_DOWN); - await assertHasFocus(t, ex.buttonSelectors[1], 'down arrow moves focus from first to second button'); - - await buttons[1].sendKeys(Key.ARROW_RIGHT); - await assertHasFocus(t, ex.buttonSelectors[2], 'right arrow moves focus from second to third button'); - - await buttons[2].sendKeys(Key.ARROW_RIGHT); - await assertHasFocus(t, ex.buttonSelectors[2], 'right arrow does not move focus from last button'); - - await buttons[0].sendKeys(Key.ARROW_LEFT); - await assertHasFocus(t, ex.buttonSelectors[0], 'left arrow does not move focus from first button'); - - await buttons[1].sendKeys(Key.ARROW_LEFT); - await assertHasFocus(t, ex.buttonSelectors[0], 'left arrow moves focus from second to first button'); - - await buttons[1].sendKeys(Key.ARROW_UP); - await assertHasFocus(t, ex.buttonSelectors[0], 'up arrow moves focus from second to first button'); - - await buttons[2].sendKeys(Key.ARROW_LEFT); - await assertHasFocus(t, ex.buttonSelectors[1], 'left arrow moves focus from third to second button'); -}); - -ariaTest('down arrow moves focus from button to open menu', exampleFile, 'key-arrows', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - const menu = await t.context.session.findElement(By.css(ex.menuSelectors[0])); - - // open menu - await buttons[0].click(); - await menu.isDisplayed(); - - await buttons[0].sendKeys(Key.ARROW_DOWN); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:first-child a`, 'down arrow moves focus to open menu'); - - await buttons[1].sendKeys(Key.ARROW_DOWN); - await assertHasFocus(t, ex.buttonSelectors[2], 'down arrow moves focus to next button if active button\'s menu is closed'); -}); - -ariaTest('home and end move focus to first and last buttons', exampleFile, 'key-home-end', async (t) => { - - const buttons = await t.context.queryElements(t, ex.buttonSelector); - - await buttons[1].sendKeys(Key.HOME); - await assertHasFocus(t, ex.buttonSelectors[0], 'home key moves focus to first button'); - - await buttons[0].sendKeys(Key.END); - await assertHasFocus(t, ex.buttonSelectors[2], 'end key moves focus to last button'); -}); - -ariaTest('arrow keys move focus between open menu links', exampleFile, 'key-arrows', async (t) => { - - const button = await t.context.session.findElement(By.css(ex.buttonSelectors[0])); - const menu = await t.context.session.findElement(By.css(ex.menuSelectors[0])); - const menuLinks = await t.context.queryElements(t, `${ex.menuSelectors[0]} a`); - - await button.click(); - await menu.isDisplayed(); - - await menuLinks[0].sendKeys(Key.ARROW_DOWN); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:nth-child(2) a`, 'down arrow moves focus from first to second link'); - - await menuLinks[0].sendKeys(Key.ARROW_RIGHT); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:nth-child(2) a`, 'right arrow moves focus from first to second link'); - - await menuLinks[2].sendKeys(Key.ARROW_DOWN); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:last-child a`, 'down arrow does not move focus from last link'); - - await menuLinks[0].sendKeys(Key.ARROW_UP); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:nth-child(1) a`, 'up arrow does not move focus from first link'); - - await menuLinks[1].sendKeys(Key.ARROW_LEFT); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:nth-child(1) a`, 'left arrow moves focus from second to first link'); - - await menuLinks[1].sendKeys(Key.ARROW_UP); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:nth-child(1) a`, 'up arrow moves focus from second to first link'); -}); - -ariaTest('home and end move focus to first and last open menu link', exampleFile, 'key-home-end', async (t) => { - - const button = await t.context.session.findElement(By.css(ex.buttonSelectors[0])); - const menu = await t.context.session.findElement(By.css(ex.menuSelectors[0])); - const menuLinks = await t.context.queryElements(t, `${ex.menuSelectors[0]} a`); - - await button.click(); - await menu.isDisplayed(); - - await menuLinks[1].sendKeys(Key.HOME); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:nth-child(1) a`, 'home key moves focus to first link'); - - await menuLinks[0].sendKeys(Key.END); - await assertHasFocus(t, `${ex.menuSelectors[0]} li:last-child a`, 'end key moves focus to last link'); -}); +'use strict'; + +const { ariaTest } = require('..'); +const { By, Key } = require('selenium-webdriver'); +const assertAriaControls = require('../util/assertAriaControls'); +const assertAttributeValues = require('../util/assertAttributeValues'); +const assertTabOrder = require('../util/assertTabOrder'); +const assertHasFocus = require('../util/assertHasFocus'); + +const exampleFile = 'disclosure/disclosure-navigation.html'; + +const ex = { + buttonSelector: '#exTest button', + menuSelector: '#exTest > li > ul', + linkSelector: '#exTest > li > ul a', + buttonSelectors: [ + '#exTest > li:nth-child(1) button', + '#exTest > li:nth-child(2) button', + '#exTest > li:nth-child(3) button', + ], + menuSelectors: [ + '#exTest > li:nth-child(1) ul', + '#exTest > li:nth-child(2) ul', + '#exTest > li:nth-child(3) ul', + ], +}; + +// Attributes + +ariaTest( + '"aria-controls" attribute on button', + exampleFile, + 'button-aria-controls', + async (t) => { + await assertAriaControls(t, ex.buttonSelector); + } +); + +ariaTest( + '"aria-expanded" attribute on button', + exampleFile, + 'button-aria-expanded', + async (t) => { + await assertAttributeValues(t, ex.buttonSelector, 'aria-expanded', 'false'); + + let buttons = await t.context.queryElements(t, ex.buttonSelector); + let menus = await t.context.queryElements(t, ex.menuSelector); + for (let i = buttons.length - 1; i >= 0; i--) { + await buttons[i].click(); + t.true( + await menus[i].isDisplayed(), + 'Each dropdown menu should display after clicking its trigger' + ); + await assertAttributeValues( + t, + ex.buttonSelectors[i], + 'aria-expanded', + 'true' + ); + } + } +); + +ariaTest( + '"aria-current" attribute on links', + exampleFile, + 'link-aria-current', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + for (let b = 0; b < buttons.length; b++) { + const links = await t.context.queryElements(t, 'a', menus[b]); + + for (let l = 0; l < links.length; l++) { + await buttons[b].click(); + await links[l].click(); + + t.is( + await links[l].getAttribute('aria-current'), + 'page', + 'after clicking link at index ' + + l + + ' on menu ' + + b + + 'aria-current should be set to page' + ); + + let ariaCurrentLinks = await t.context.queryElements( + t, + `${ex.linkSelector}[aria-current="page"]` + ); + + t.is( + ariaCurrentLinks.length, + 1, + 'after clicking link at index ' + + l + + ' on menu ' + + b + + ', only one link should have aria-current set' + ); + } + } + } +); + +// Keys + +ariaTest( + 'TAB should move focus between buttons', + exampleFile, + 'key-tab', + async (t) => { + await assertTabOrder(t, ex.buttonSelectors); + } +); + +ariaTest( + 'key ENTER expands dropdown', + exampleFile, + 'key-enter-space', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + for (let i = buttons.length - 1; i >= 0; i--) { + await buttons[i].sendKeys(Key.ENTER); + await assertAttributeValues( + t, + ex.buttonSelectors[i], + 'aria-expanded', + 'true' + ); + t.true( + await menus[i].isDisplayed(), + 'Dropdown menu should display sending ENTER to its trigger' + ); + + await buttons[i].sendKeys(Key.ENTER); + await assertAttributeValues( + t, + ex.buttonSelectors[i], + 'aria-expanded', + 'false' + ); + t.false( + await menus[i].isDisplayed(), + 'Dropdown menu should close after sending ENTER twice to its trigger' + ); + } + } +); + +ariaTest( + 'key SPACE expands dropdown', + exampleFile, + 'key-enter-space', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + for (let i = buttons.length - 1; i >= 0; i--) { + await buttons[i].sendKeys(Key.SPACE); + await assertAttributeValues( + t, + ex.buttonSelectors[i], + 'aria-expanded', + 'true' + ); + t.true( + await menus[i].isDisplayed(), + 'Dropdown menu should display sending SPACE to its trigger' + ); + + await buttons[i].sendKeys(Key.SPACE); + await assertAttributeValues( + t, + ex.buttonSelectors[i], + 'aria-expanded', + 'false' + ); + t.false( + await menus[i].isDisplayed(), + 'Dropdown menu should close after sending SPACE twice to its trigger' + ); + } + } +); + +ariaTest('key ESCAPE closes dropdown', exampleFile, 'key-escape', async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelectors[0]) + ); + const menu = await t.context.session.findElement(By.css(ex.menuSelectors[0])); + const firstLink = await t.context.session.findElement( + By.css(`${ex.menuSelectors[0]} a`) + ); + + await button.click(); + t.true(await menu.isDisplayed(), 'Dropdown menu is displayed on click'); + + await firstLink.sendKeys(Key.ESCAPE); + await assertAttributeValues( + t, + ex.buttonSelectors[0], + 'aria-expanded', + 'false' + ); + t.false( + await menu.isDisplayed(), + 'Dropdown menu should close after sending ESCAPE to the menu' + ); +}); + +ariaTest( + 'arrow keys move focus between disclosure buttons', + exampleFile, + 'key-arrows', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + await buttons[0].sendKeys(Key.ARROW_RIGHT); + await assertHasFocus( + t, + ex.buttonSelectors[1], + 'right arrow moves focus from first to second button' + ); + + await buttons[0].sendKeys(Key.ARROW_DOWN); + await assertHasFocus( + t, + ex.buttonSelectors[1], + 'down arrow moves focus from first to second button' + ); + + await buttons[1].sendKeys(Key.ARROW_RIGHT); + await assertHasFocus( + t, + ex.buttonSelectors[2], + 'right arrow moves focus from second to third button' + ); + + await buttons[2].sendKeys(Key.ARROW_RIGHT); + await assertHasFocus( + t, + ex.buttonSelectors[2], + 'right arrow does not move focus from last button' + ); + + await buttons[0].sendKeys(Key.ARROW_LEFT); + await assertHasFocus( + t, + ex.buttonSelectors[0], + 'left arrow does not move focus from first button' + ); + + await buttons[1].sendKeys(Key.ARROW_LEFT); + await assertHasFocus( + t, + ex.buttonSelectors[0], + 'left arrow moves focus from second to first button' + ); + + await buttons[1].sendKeys(Key.ARROW_UP); + await assertHasFocus( + t, + ex.buttonSelectors[0], + 'up arrow moves focus from second to first button' + ); + + await buttons[2].sendKeys(Key.ARROW_LEFT); + await assertHasFocus( + t, + ex.buttonSelectors[1], + 'left arrow moves focus from third to second button' + ); + } +); + +ariaTest( + 'down arrow moves focus from button to open menu', + exampleFile, + 'key-arrows', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + const menu = await t.context.session.findElement( + By.css(ex.menuSelectors[0]) + ); + + // open menu + await buttons[0].click(); + await menu.isDisplayed(); + + await buttons[0].sendKeys(Key.ARROW_DOWN); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:first-child a`, + 'down arrow moves focus to open menu' + ); + + await buttons[1].sendKeys(Key.ARROW_DOWN); + await assertHasFocus( + t, + ex.buttonSelectors[2], + "down arrow moves focus to next button if active button's menu is closed" + ); + } +); + +ariaTest( + 'home and end move focus to first and last buttons', + exampleFile, + 'key-home-end', + async (t) => { + const buttons = await t.context.queryElements(t, ex.buttonSelector); + + await buttons[1].sendKeys(Key.HOME); + await assertHasFocus( + t, + ex.buttonSelectors[0], + 'home key moves focus to first button' + ); + + await buttons[0].sendKeys(Key.END); + await assertHasFocus( + t, + ex.buttonSelectors[2], + 'end key moves focus to last button' + ); + } +); + +ariaTest( + 'arrow keys move focus between open menu links', + exampleFile, + 'key-arrows', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelectors[0]) + ); + const menu = await t.context.session.findElement( + By.css(ex.menuSelectors[0]) + ); + const menuLinks = await t.context.queryElements( + t, + `${ex.menuSelectors[0]} a` + ); + + await button.click(); + await menu.isDisplayed(); + + await menuLinks[0].sendKeys(Key.ARROW_DOWN); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:nth-child(2) a`, + 'down arrow moves focus from first to second link' + ); + + await menuLinks[0].sendKeys(Key.ARROW_RIGHT); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:nth-child(2) a`, + 'right arrow moves focus from first to second link' + ); + + await menuLinks[2].sendKeys(Key.ARROW_DOWN); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:last-child a`, + 'down arrow does not move focus from last link' + ); + + await menuLinks[0].sendKeys(Key.ARROW_UP); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:nth-child(1) a`, + 'up arrow does not move focus from first link' + ); + + await menuLinks[1].sendKeys(Key.ARROW_LEFT); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:nth-child(1) a`, + 'left arrow moves focus from second to first link' + ); + + await menuLinks[1].sendKeys(Key.ARROW_UP); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:nth-child(1) a`, + 'up arrow moves focus from second to first link' + ); + } +); + +ariaTest( + 'home and end move focus to first and last open menu link', + exampleFile, + 'key-home-end', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelectors[0]) + ); + const menu = await t.context.session.findElement( + By.css(ex.menuSelectors[0]) + ); + const menuLinks = await t.context.queryElements( + t, + `${ex.menuSelectors[0]} a` + ); + + await button.click(); + await menu.isDisplayed(); + + await menuLinks[1].sendKeys(Key.HOME); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:nth-child(1) a`, + 'home key moves focus to first link' + ); + + await menuLinks[0].sendKeys(Key.END); + await assertHasFocus( + t, + `${ex.menuSelectors[0]} li:last-child a`, + 'end key moves focus to last link' + ); + } +); diff --git a/test/tests/feed_feed.js b/test/tests/feed_feed.js index 062029a3ea..99d35d210b 100644 --- a/test/tests/feed_feed.js +++ b/test/tests/feed_feed.js @@ -17,7 +17,7 @@ const ex = { articleSelector: '[role="article"]', timeToLoad10Articles: 2500, numArticlesLoadedInSet: 10, - delayTimeSelector: '#delay-time-select' + delayTimeSelector: '#delay-time-select', }; const navigateToFeed = async function (t) { @@ -25,7 +25,7 @@ const navigateToFeed = async function (t) { return t.context.session.wait( () => { - return t.context.session.getCurrentUrl().then(url => { + return t.context.session.getCurrentUrl().then((url) => { return url != t.context.url; }); }, @@ -38,179 +38,251 @@ const waitForArticlesToLoad = async function (t) { // Wait for articles to load return t.context.session.wait( async function () { - let element = await t.context.session.findElement(By.css(ex.feedSelector)); + let element = await t.context.session.findElement( + By.css(ex.feedSelector) + ); return (await element.getAttribute('aria-busy')) !== 'true'; }, ex.timeToLoad10Articles, - 'Expected to have articles loaded on after page load after: ' + ex.timeToLoad10Articles + 'Expected to have articles loaded on after page load after: ' + + ex.timeToLoad10Articles ); }; const loadMoreArticles = async function (t) { return t.context.session.executeScript(() => { - window.scrollBy(0,2000); + window.scrollBy(0, 2000); }); }; const checkFocus = async function (t, selector, index) { return t.context.session.wait( () => { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }, t.context.waitTime, - 'Timeout: Focus did not reach item at index ' + index + ' of items: ' + selector + 'Timeout: Focus did not reach item at index ' + + index + + ' of items: ' + + selector ); }; // Attributes ariaTest('role="feed" exists', exampleFile, 'feed-role', async (t) => { - await navigateToFeed(t); + await navigateToFeed(t); await assertAriaRoles(t, 'main-content', 'feed', 1, 'div'); }); // This bug has been reported in issue: https://github.com/w3c/aria-practices/issues/911 -ariaTest.failing('aria-labelledby attribute on feed element', exampleFile, 'feed-aria-labelledby', async (t) => { +ariaTest.failing( + 'aria-labelledby attribute on feed element', + exampleFile, + 'feed-aria-labelledby', + async (t) => { await navigateToFeed(t); - await assertAriaLabelledby(t, ex.feedSelector); -}); + await assertAriaLabelledby(t, ex.feedSelector); + } +); -ariaTest('aria-busy attribute on feed element', exampleFile, 'feed-aria-busy', async (t) => { +ariaTest( + 'aria-busy attribute on feed element', + exampleFile, + 'feed-aria-busy', + async (t) => { await navigateToFeed(t); - await assertAttributeValues(t, ex.feedSelector, 'aria-busy', 'true'); - await waitForArticlesToLoad(t); - await assertAttributeDNE(t, ex.feedSelector, 'aria-busy'); -}); + await assertAttributeValues(t, ex.feedSelector, 'aria-busy', 'true'); + await waitForArticlesToLoad(t); + await assertAttributeDNE(t, ex.feedSelector, 'aria-busy'); + } +); ariaTest('role="article" exists', exampleFile, 'article-role', async (t) => { - await navigateToFeed(t); + await navigateToFeed(t); await waitForArticlesToLoad(t); await assertAriaRoles(t, 'main-content', 'article', 10, 'div'); }); -ariaTest('tabindex="-1" on article elements', exampleFile, 'article-tabindex', async (t) => { +ariaTest( + 'tabindex="-1" on article elements', + exampleFile, + 'article-tabindex', + async (t) => { await navigateToFeed(t); - await waitForArticlesToLoad(t); - await assertAttributeValues(t, ex.articleSelector, 'tabindex', '0'); -}); - -ariaTest('aria-labelledby set on article elements', exampleFile, 'article-labelledby', async (t) => { - await navigateToFeed(t); - await waitForArticlesToLoad(t); - await assertAriaLabelledby(t, ex.articleSelector); -}); - -ariaTest('aria-describedby set on article elements', exampleFile, 'article-describedby', async (t) => { - await navigateToFeed(t); - await waitForArticlesToLoad(t); - await assertAriaDescribedby(t, ex.articleSelector); -}); + await waitForArticlesToLoad(t); + await assertAttributeValues(t, ex.articleSelector, 'tabindex', '0'); + } +); -ariaTest('aria-posinset on article element', exampleFile, 'article-aria-posinset', async (t) => { +ariaTest( + 'aria-labelledby set on article elements', + exampleFile, + 'article-labelledby', + async (t) => { await navigateToFeed(t); - await waitForArticlesToLoad(t); - - let articles = await t.context.queryElements(t, ex.articleSelector); - for (let index = 1; index <= articles.length; index++) { - t.is( - await articles[index - 1].getAttribute('aria-posinset'), - index.toString(), - 'Article number ' + index + ' does not have aria-posinset set correctly.' - ); + await waitForArticlesToLoad(t); + await assertAriaLabelledby(t, ex.articleSelector); } +); - await loadMoreArticles(t); - await waitForArticlesToLoad(t); - - articles = await t.context.queryElements(t, ex.articleSelector); - for (let index = 1; index <= articles.length; index++) { - t.is( - await articles[index - 1].getAttribute('aria-posinset'), - index.toString(), - 'Article number ' + index + ' does not have aria-posinset set correctly.' - ); +ariaTest( + 'aria-describedby set on article elements', + exampleFile, + 'article-describedby', + async (t) => { + await navigateToFeed(t); + await waitForArticlesToLoad(t); + await assertAriaDescribedby(t, ex.articleSelector); } -}); +); -ariaTest('aria-setsize on article element', exampleFile, 'article-aria-setsize', async (t) => { - - await navigateToFeed(t); - await waitForArticlesToLoad(t); - - let articles = await t.context.queryElements(t, ex.articleSelector); - let numArticles = articles.length; - for (let index = 1; index <= numArticles; index++) { - t.is( - await articles[index - 1].getAttribute('aria-setsize'), - numArticles.toString(), - 'Article number ' + index + ' does not have aria-setsize set correctly, ' + - 'after first load.' - ); +ariaTest( + 'aria-posinset on article element', + exampleFile, + 'article-aria-posinset', + async (t) => { + await navigateToFeed(t); + await waitForArticlesToLoad(t); + + let articles = await t.context.queryElements(t, ex.articleSelector); + for (let index = 1; index <= articles.length; index++) { + t.is( + await articles[index - 1].getAttribute('aria-posinset'), + index.toString(), + 'Article number ' + + index + + ' does not have aria-posinset set correctly.' + ); + } + + await loadMoreArticles(t); + await waitForArticlesToLoad(t); + + articles = await t.context.queryElements(t, ex.articleSelector); + for (let index = 1; index <= articles.length; index++) { + t.is( + await articles[index - 1].getAttribute('aria-posinset'), + index.toString(), + 'Article number ' + + index + + ' does not have aria-posinset set correctly.' + ); + } } +); - await loadMoreArticles(t); - await waitForArticlesToLoad(t); - - articles = await t.context.queryElements(t, ex.articleSelector); - numArticles = articles.length; - for (let index = 1; index <= numArticles; index++) { - t.is( - await articles[index - 1].getAttribute('aria-setsize'), - (ex.numArticlesLoadedInSet * 2).toString(), - 'Article number ' + index + ' does not have aria-setsize set correctly, ' + - 'after triggering more articles to load.' - ); +ariaTest( + 'aria-setsize on article element', + exampleFile, + 'article-aria-setsize', + async (t) => { + await navigateToFeed(t); + await waitForArticlesToLoad(t); + + let articles = await t.context.queryElements(t, ex.articleSelector); + let numArticles = articles.length; + for (let index = 1; index <= numArticles; index++) { + t.is( + await articles[index - 1].getAttribute('aria-setsize'), + numArticles.toString(), + 'Article number ' + + index + + ' does not have aria-setsize set correctly, ' + + 'after first load.' + ); + } + + await loadMoreArticles(t); + await waitForArticlesToLoad(t); + + articles = await t.context.queryElements(t, ex.articleSelector); + numArticles = articles.length; + for (let index = 1; index <= numArticles; index++) { + t.is( + await articles[index - 1].getAttribute('aria-setsize'), + (ex.numArticlesLoadedInSet * 2).toString(), + 'Article number ' + + index + + ' does not have aria-setsize set correctly, ' + + 'after triggering more articles to load.' + ); + } } -}); +); // Keys -ariaTest('PAGE DOWN moves focus between articles', exampleFile, 'key-page-down', async (t) => { +ariaTest( + 'PAGE DOWN moves focus between articles', + exampleFile, + 'key-page-down', + async (t) => { await navigateToFeed(t); - await waitForArticlesToLoad(t); + await waitForArticlesToLoad(t); - let articles = await t.context.queryElements(t, ex.articleSelector); - articles[0].sendKeys(Key.PAGE_DOWN); + let articles = await t.context.queryElements(t, ex.articleSelector); + articles[0].sendKeys(Key.PAGE_DOWN); - t.true( - await checkFocus(t, ex.articleSelector, 1), - 'Focus should be on next article after sending PAGE DOWN to first article' - ); -}); + t.true( + await checkFocus(t, ex.articleSelector, 1), + 'Focus should be on next article after sending PAGE DOWN to first article' + ); + } +); -ariaTest('PAGE UP moves focus between articles', exampleFile, 'key-page-up', async (t) => { +ariaTest( + 'PAGE UP moves focus between articles', + exampleFile, + 'key-page-up', + async (t) => { await navigateToFeed(t); - await waitForArticlesToLoad(t); + await waitForArticlesToLoad(t); - let articles = await t.context.queryElements(t, ex.articleSelector); - articles[1].sendKeys(Key.PAGE_UP); + let articles = await t.context.queryElements(t, ex.articleSelector); + articles[1].sendKeys(Key.PAGE_UP); - t.true( - await checkFocus(t, ex.articleSelector, 0), - 'Focus should be on first article after sending PAGE UP to last article' - ); -}); + t.true( + await checkFocus(t, ex.articleSelector, 0), + 'Focus should be on first article after sending PAGE UP to last article' + ); + } +); -ariaTest('CONTROL+END moves focus out of feed', exampleFile, 'key-control-end', async (t) => { +ariaTest( + 'CONTROL+END moves focus out of feed', + exampleFile, + 'key-control-end', + async (t) => { await navigateToFeed(t); - await waitForArticlesToLoad(t); - - let articles = await t.context.queryElements(t, ex.articleSelector); - articles[0].sendKeys(Key.chord(Key.CONTROL, Key.END)); + await waitForArticlesToLoad(t); - t.true( - await checkFocus(t, ex.delayTimeSelector, 0), - 'Focus should move off the feed (onto element: ' + ex.delayTimeSelector + ') after sending keys CONTROL+END' - ); + let articles = await t.context.queryElements(t, ex.articleSelector); + articles[0].sendKeys(Key.chord(Key.CONTROL, Key.END)); -}); + t.true( + await checkFocus(t, ex.delayTimeSelector, 0), + 'Focus should move off the feed (onto element: ' + + ex.delayTimeSelector + + ') after sending keys CONTROL+END' + ); + } +); // This bug has been reported in issue: https://github.com/w3c/aria-practices/issues/911 -ariaTest.failing('key home moves focus out of feed', exampleFile, 'key-control-home', async (t) => { - t.fail(); -}); - +ariaTest.failing( + 'key home moves focus out of feed', + exampleFile, + 'key-control-home', + async (t) => { + t.fail(); + } +); diff --git a/test/tests/grid_LayoutGrids.js b/test/tests/grid_LayoutGrids.js index f78821ffe6..894293786e 100644 --- a/test/tests/grid_LayoutGrids.js +++ b/test/tests/grid_LayoutGrids.js @@ -19,35 +19,43 @@ const clickUntilDisabled = async (session, selector) => { }; const checkActiveElement = async function (t, gridcellsSelector, index) { - return t.context.session.executeScript(function () { - - const gridcellsSelector = arguments[0]; - const index = arguments[1]; - const gridcells = document.querySelectorAll(gridcellsSelector); - const gridcell = gridcells[index]; - return (document.activeElement === gridcell) || gridcell.contains(document.activeElement); - - }, gridcellsSelector, index); + return t.context.session.executeScript( + function () { + const gridcellsSelector = arguments[0]; + const index = arguments[1]; + const gridcells = document.querySelectorAll(gridcellsSelector); + const gridcell = gridcells[index]; + return ( + document.activeElement === gridcell || + gridcell.contains(document.activeElement) + ); + }, + gridcellsSelector, + index + ); }; const focusOnOrInCell = async function (t, cellElement, focusable) { - return t.context.session.executeScript(function () { - const [cellElement, focusable] = arguments; - - if (focusable == 'gridcell') { - cellElement.focus(); - if (document.activeElement === cellElement) { - return cellElement; + return t.context.session.executeScript( + function () { + const [cellElement, focusable] = arguments; + + if (focusable == 'gridcell') { + cellElement.focus(); + if (document.activeElement === cellElement) { + return cellElement; + } } - } - - let focusableEl = cellElement.querySelector(focusable); - focusableEl.focus(); - if (document.activeElement === focusableEl) { - return focusableEl; - } - }, cellElement, focusable); + let focusableEl = cellElement.querySelector(focusable); + focusableEl.focus(); + if (document.activeElement === focusableEl) { + return focusableEl; + } + }, + cellElement, + focusable + ); }; const findColIndex = function () { @@ -58,91 +66,145 @@ const findColIndex = function () { return el.attribute('aria-colindex'); }; - const pageExamples = { - 'ex1': { + ex1: { gridSelector: '#ex1 [role="grid"]', gridcellSelector: '#ex1 [role="gridcell"]', focusableElements: [ - 'a', 'a', 'a', 'a', 'a', 'a' // row 1 - ] + 'a', + 'a', + 'a', + 'a', + 'a', + 'a', // row 1 + ], }, - 'ex2': { + ex2: { gridSelector: '#ex2 [role="grid"]', gridcellSelector: '#ex2 [role="gridcell"]', focusableElements: [ - 'a', 'span[role="button"]', // row 1 - 'a', 'span[role="button"]' // row 2 - ] + 'a', + 'span[role="button"]', // row 1 + 'a', + 'span[role="button"]', // row 2 + ], }, - 'ex3': { + ex3: { gridSelector: '#ex3 [role="grid"]', gridcellSelector: '#ex3 [role="gridcell"]', focusableElements: [ - 'a', 'gridcell', 'gridcell', // row 1 - 'a', 'gridcell', 'gridcell', // row 2 - 'a', 'gridcell', 'gridcell', // row 3 - 'a', 'gridcell', 'gridcell', // row 4 - 'a', 'gridcell', 'gridcell', // row 5 - 'a', 'gridcell', 'gridcell', // row 6 - 'a', 'gridcell', 'gridcell', // row 7 - 'a', 'gridcell', 'gridcell', // row 8 - 'a', 'gridcell', 'gridcell', // row 9 - 'a', 'gridcell', 'gridcell', // row 10 - 'a', 'gridcell', 'gridcell', // row 11 - 'a', 'gridcell', 'gridcell', // row 12 - 'a', 'gridcell', 'gridcell', // row 13 - 'a', 'gridcell', 'gridcell', // row 14 - 'a', 'gridcell', 'gridcell', // row 15 - 'a', 'gridcell', 'gridcell', // row 16 - 'a', 'gridcell', 'gridcell', // row 17 - 'a', 'gridcell', 'gridcell', // row 18 - 'a', 'gridcell', 'gridcell' // row 19 - ] - } + 'a', + 'gridcell', + 'gridcell', // row 1 + 'a', + 'gridcell', + 'gridcell', // row 2 + 'a', + 'gridcell', + 'gridcell', // row 3 + 'a', + 'gridcell', + 'gridcell', // row 4 + 'a', + 'gridcell', + 'gridcell', // row 5 + 'a', + 'gridcell', + 'gridcell', // row 6 + 'a', + 'gridcell', + 'gridcell', // row 7 + 'a', + 'gridcell', + 'gridcell', // row 8 + 'a', + 'gridcell', + 'gridcell', // row 9 + 'a', + 'gridcell', + 'gridcell', // row 10 + 'a', + 'gridcell', + 'gridcell', // row 11 + 'a', + 'gridcell', + 'gridcell', // row 12 + 'a', + 'gridcell', + 'gridcell', // row 13 + 'a', + 'gridcell', + 'gridcell', // row 14 + 'a', + 'gridcell', + 'gridcell', // row 15 + 'a', + 'gridcell', + 'gridcell', // row 16 + 'a', + 'gridcell', + 'gridcell', // row 17 + 'a', + 'gridcell', + 'gridcell', // row 18 + 'a', + 'gridcell', + 'gridcell', // row 19 + ], + }, }; const exampleInitialized = async function (t, exId) { const initializedSelector = '#' + exId + ' [role="grid"] [tabindex="0"]'; - await t.context.session.wait(async function () { - const els = await t.context.queryElements(t, initializedSelector); - return els.length === 1; - }, t.context.waitTime, 'Timeout waiting for widget to initialize before running tests.'); + await t.context.session.wait( + async function () { + const els = await t.context.queryElements(t, initializedSelector); + return els.length === 1; + }, + t.context.waitTime, + 'Timeout waiting for widget to initialize before running tests.' + ); }; // Attributes -ariaTest('Test "role=grid" attribute exists', - 'grid/LayoutGrids.html', 'grid-role', async (t) => { - - +ariaTest( + 'Test "role=grid" attribute exists', + 'grid/LayoutGrids.html', + 'grid-role', + async (t) => { for (let exId in pageExamples) { const ex = pageExamples[exId]; t.is( (await t.context.queryElements(t, ex.gridSelector)).length, 1, - 'One "role=grid" element should be found by selector: ' + ex.gridSelector + 'One "role=grid" element should be found by selector: ' + + ex.gridSelector ); } - }); - -ariaTest('Test "aria-labelledby" attribute exists', - 'grid/LayoutGrids.html', 'aria-labelledby', async (t) => { - + } +); +ariaTest( + 'Test "aria-labelledby" attribute exists', + 'grid/LayoutGrids.html', + 'aria-labelledby', + async (t) => { for (let exId in pageExamples) { const ex = pageExamples[exId]; await assertAriaLabelledby(t, ex.gridSelector); } - }); - -ariaTest('Test "aria-rowcount" attribute exists', - 'grid/LayoutGrids.html', 'aria-rowcount', async (t) => { - + } +); +ariaTest( + 'Test "aria-rowcount" attribute exists', + 'grid/LayoutGrids.html', + 'aria-rowcount', + async (t) => { // This test only applies to example 3 const gridSelector = '#ex3 [role="grid"]'; const gridLocator = By.css(gridSelector); @@ -152,91 +214,121 @@ ariaTest('Test "aria-rowcount" attribute exists', t.truthy( rowCount, - '"aria-rowcount" attribute should exist on element selected by: ' + gridSelector + '"aria-rowcount" attribute should exist on element selected by: ' + + gridSelector ); const gridElement = await t.context.session.findElement(gridLocator); - const rowElements = await t.context.queryElements(t, '[role="row"]', gridElement); + const rowElements = await t.context.queryElements( + t, + '[role="row"]', + gridElement + ); t.is( rowElements.length, Number(rowCount), - '"aria-rowcount" attribute should match the number of [role="row"] divs in example: ' + gridSelector + '"aria-rowcount" attribute should match the number of [role="row"] divs in example: ' + + gridSelector ); - }); - -ariaTest('Test "role=row" attribute exists', - 'grid/LayoutGrids.html', 'row-role', async (t) => { - + } +); +ariaTest( + 'Test "role=row" attribute exists', + 'grid/LayoutGrids.html', + 'row-role', + async (t) => { for (let exId in pageExamples) { const ex = pageExamples[exId]; const gridLocator = By.css(ex.gridSelector); const gridElement = await t.context.session.findElement(gridLocator); - const rowElements = await t.context.queryElements(t, 'div[role="row"]', gridElement); + const rowElements = await t.context.queryElements( + t, + 'div[role="row"]', + gridElement + ); t.truthy( rowElements.length, '"role=row" elements should exist in example: ' + ex.gridSelector ); } - }); - -ariaTest('test "aria-rowindex" attribute exists', - 'grid/LayoutGrids.html', 'aria-rowindex', async (t) => { - + } +); +ariaTest( + 'test "aria-rowindex" attribute exists', + 'grid/LayoutGrids.html', + 'aria-rowindex', + async (t) => { // This test only applies to example 3 const gridSelector = '#ex3 [role="grid"]'; const gridLocator = By.css(gridSelector); const gridElement = await t.context.session.findElement(gridLocator); - const rowElements = await t.context.queryElements(t, '[role="row"]', gridElement); + const rowElements = await t.context.queryElements( + t, + '[role="row"]', + gridElement + ); for (let i = 0; i < rowElements.length; i++) { const value = (i + 1).toString(); t.is( await rowElements[i].getAttribute('aria-rowindex'), value, - '[aria-rowindex="' + value + '"] attribute added to role="row" elements in: ' + gridSelector + '[aria-rowindex="' + + value + + '"] attribute added to role="row" elements in: ' + + gridSelector ); } + } +); - }); - -ariaTest('Test "role=gridcell" attribute exists', - 'grid/LayoutGrids.html', 'gridcell-role', async (t) => { - - +ariaTest( + 'Test "role=gridcell" attribute exists', + 'grid/LayoutGrids.html', + 'gridcell-role', + async (t) => { for (let exId in pageExamples) { const ex = pageExamples[exId]; const gridLocator = By.css(ex.gridSelector); const gridElement = await t.context.session.findElement(gridLocator); - const gridcellElements = await t.context.queryElements(t, '[role="gridcell"]', gridElement); + const gridcellElements = await t.context.queryElements( + t, + '[role="gridcell"]', + gridElement + ); t.truthy( gridcellElements.length, '["role=gridcell]" elements should exist in example: ' + ex.gridSelector ); } - }); - - -ariaTest('Test "tabindex" appropriately set', - 'grid/LayoutGrids.html', 'tabindex', async (t) => { + } +); +ariaTest( + 'Test "tabindex" appropriately set', + 'grid/LayoutGrids.html', + 'tabindex', + async (t) => { for (let exId in pageExamples) { const ex = pageExamples[exId]; // Wait for the javascript to run before testing example await exampleInitialized(t, exId); - const gridcellElements = await t.context.queryElements(t, ex.gridcellSelector); + const gridcellElements = await t.context.queryElements( + t, + ex.gridcellSelector + ); for (let el = 0; el < gridcellElements.length; el++) { - // The first gridcell element will have tabindex=0 const tabindex = el === 0 ? '0' : '-1'; @@ -248,7 +340,12 @@ ariaTest('Test "tabindex" appropriately set', t.is( await gridcellElements[el].getAttribute('tabindex'), tabindex, - 'gridcell at index ' + el + 'in ' + exId + ' grid should have tabindex=' + tabindex + 'gridcell at index ' + + el + + 'in ' + + exId + + ' grid should have tabindex=' + + tabindex ); } // If it is not the gridcell, it is an element within it @@ -256,7 +353,11 @@ ariaTest('Test "tabindex" appropriately set', t.is( await gridcellElements[el].getAttribute('tabindex'), null, - 'gridcell at index ' + el + 'in ' + exId + ' grid should not have tabindex' + 'gridcell at index ' + + el + + 'in ' + + exId + + ' grid should not have tabindex' ); t.is( @@ -264,468 +365,685 @@ ariaTest('Test "tabindex" appropriately set', .findElement(By.css(focusableElementSelector)) .getAttribute('tabindex'), tabindex, - 'element "' + focusableElementSelector + '" in gridcell index ' + - el + 'in ' + exId + ' grid should have tabindex=' + tabindex + 'element "' + + focusableElementSelector + + '" in gridcell index ' + + el + + 'in ' + + exId + + ' grid should have tabindex=' + + tabindex ); - } - } } - }); - + } +); // Keys -ariaTest('Right arrow key moves focus', 'grid/LayoutGrids.html', 'key-right-arrow', async (t) => { - - for (let [exId, ex] of Object.entries(pageExamples)) { - const gridcellElements = await t.context.queryElements(t, ex.gridcellSelector); +ariaTest( + 'Right arrow key moves focus', + 'grid/LayoutGrids.html', + 'key-right-arrow', + async (t) => { + for (let [exId, ex] of Object.entries(pageExamples)) { + const gridcellElements = await t.context.queryElements( + t, + ex.gridcellSelector + ); - // Find the first focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[0], ex.focusableElements[0]); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the first gridcell: ' + ex.gridcellSelector); - } + // Find the first focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[0], + ex.focusableElements[0] + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the first gridcell: ' + + ex.gridcellSelector + ); + } - // Test focus moves to next element on arrow right + // Test focus moves to next element on arrow right + + for (let index = 1; index < gridcellElements.length; index++) { + await activeElement.sendKeys(Key.ARROW_RIGHT); + const correctActiveElement = await checkActiveElement( + t, + ex.gridcellSelector, + index + ); + + t.true( + correctActiveElement, + 'Example ' + + exId + + ': gridcell `' + + (await gridcellElements[index].getText()) + + '` should receive focus after arrow right' + ); + + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); + } - for (let index = 1; index < gridcellElements.length; index++) { + // Test arrow key on final element await activeElement.sendKeys(Key.ARROW_RIGHT); - const correctActiveElement = await checkActiveElement(t, ex.gridcellSelector, index); - + const correctActiveElement = await checkActiveElement( + t, + ex.gridcellSelector, + gridcellElements.length - 1 + ); t.true( correctActiveElement, - 'Example ' + exId + ': gridcell `' + (await gridcellElements[index].getText()) + '` should receive focus after arrow right' + 'Example ' + + exId + + ': Right Arrow sent to final gridcell should not move focus.' ); - - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); } - - // Test arrow key on final element - - await activeElement.sendKeys(Key.ARROW_RIGHT); - const correctActiveElement = await checkActiveElement(t, ex.gridcellSelector, gridcellElements.length - 1); - t.true( - correctActiveElement, - 'Example ' + exId + ': Right Arrow sent to final gridcell should not move focus.' - ); } -}); - -ariaTest('Left arrow key moves focus', 'grid/LayoutGrids.html', 'key-left-arrow', async (t) => { - - for (let [exId, ex] of Object.entries(pageExamples)) { - - if (exId == 'ex3') { - // This test depends on the "page down" button which is not specified by - // the widget's description. It does this to avoid relying on behaviors - // that are tested elsewhere. - await clickUntilDisabled(t.context.session, '#ex3_pagedown_button'); - } +); + +ariaTest( + 'Left arrow key moves focus', + 'grid/LayoutGrids.html', + 'key-left-arrow', + async (t) => { + for (let [exId, ex] of Object.entries(pageExamples)) { + if (exId == 'ex3') { + // This test depends on the "page down" button which is not specified by + // the widget's description. It does this to avoid relying on behaviors + // that are tested elsewhere. + await clickUntilDisabled(t.context.session, '#ex3_pagedown_button'); + } - const gridcellElements = await t.context.queryElements(t, ex.gridcellSelector); - const lastCellIndex = gridcellElements.length - 1; + const gridcellElements = await t.context.queryElements( + t, + ex.gridcellSelector + ); + const lastCellIndex = gridcellElements.length - 1; - // Find the last focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[lastCellIndex], ex.focusableElements[lastCellIndex]); + // Find the last focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[lastCellIndex], + ex.focusableElements[lastCellIndex] + ); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the last gridcell: ' + ex.gridcellSelector); - } + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the last gridcell: ' + + ex.gridcellSelector + ); + } - // Test focus moves to previous cell after arrow left + // Test focus moves to previous cell after arrow left + + for (let index = gridcellElements.length - 2; index > -1; index--) { + await activeElement.sendKeys(Key.ARROW_LEFT); + const correctActiveElement = await checkActiveElement( + t, + ex.gridcellSelector, + index + ); + + t.true( + correctActiveElement, + 'Example ' + + exId + + ': gridcell `' + + (await gridcellElements[index].getText()) + + '` should receive focus after arrow left' + ); + + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); + } - for (let index = gridcellElements.length - 2; index > -1; index--) { + // Test arrow left on the first cell await activeElement.sendKeys(Key.ARROW_LEFT); - const correctActiveElement = await checkActiveElement(t, ex.gridcellSelector, index); - + const correctActiveElement = await checkActiveElement( + t, + ex.gridcellSelector, + 0 + ); t.true( correctActiveElement, - 'Example ' + exId + ': gridcell `' + (await gridcellElements[index].getText()) + '` should receive focus after arrow left' + 'Example ' + + exId + + ': Left Arrow sent to fist gridcell should not move focus.' ); - - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); } - - // Test arrow left on the first cell - - await activeElement.sendKeys(Key.ARROW_LEFT); - const correctActiveElement = await checkActiveElement(t, ex.gridcellSelector, 0); - t.true( - correctActiveElement, - 'Example ' + exId + ': Left Arrow sent to fist gridcell should not move focus.' - ); } -}); - -ariaTest('Down arrow key moves focus', 'grid/LayoutGrids.html', 'key-down-arrow', async (t) => { - - const cellSelectors = { - ex1: '#ex1 [role="gridcell"]', - ex2: '#ex2 [role="row"] [role="gridcell"]:first-of-type', - ex3: '#ex3 [role="row"] [role="gridcell"]:first-child' - }; - - for (let [exId, selector] of Object.entries(cellSelectors)) { - const ex = pageExamples[exId]; +); + +ariaTest( + 'Down arrow key moves focus', + 'grid/LayoutGrids.html', + 'key-down-arrow', + async (t) => { + const cellSelectors = { + ex1: '#ex1 [role="gridcell"]', + ex2: '#ex2 [role="row"] [role="gridcell"]:first-of-type', + ex3: '#ex3 [role="row"] [role="gridcell"]:first-child', + }; + + for (let [exId, selector] of Object.entries(cellSelectors)) { + const ex = pageExamples[exId]; - const gridcellElements = await t.context.queryElements(t, selector); + const gridcellElements = await t.context.queryElements(t, selector); - // Find the first focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[0], ex.focusableElements[0]); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the first gridcell: ' + selector); - } + // Find the first focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[0], + ex.focusableElements[0] + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the first gridcell: ' + + selector + ); + } - // Test focus moves to next row on down arrow + // Test focus moves to next row on down arrow + + for (let index = 1; index < gridcellElements.length; index++) { + await activeElement.sendKeys(Key.ARROW_DOWN); + const correctActiveElement = await checkActiveElement( + t, + selector, + index + ); + + t.true( + correctActiveElement, + 'Example ' + + exId + + ': gridcell `' + + (await gridcellElements[index].getText()) + + '` should receive focus after arrow down' + ); + + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); + } - for (let index = 1; index < gridcellElements.length; index++) { + // Test arrow key on final row await activeElement.sendKeys(Key.ARROW_DOWN); - const correctActiveElement = await checkActiveElement(t, selector, index); - + const correctActiveElement = await checkActiveElement( + t, + selector, + gridcellElements.length - 1 + ); t.true( correctActiveElement, - 'Example ' + exId + ': gridcell `' + (await gridcellElements[index].getText()) + '` should receive focus after arrow down' + 'Example ' + + exId + + ': Down Arrow sent to final gridcell should not move focus.' ); - - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); } - - // Test arrow key on final row - - await activeElement.sendKeys(Key.ARROW_DOWN); - const correctActiveElement = await checkActiveElement(t, selector, gridcellElements.length - 1); - t.true( - correctActiveElement, - 'Example ' + exId + ': Down Arrow sent to final gridcell should not move focus.' - ); } -}); - -ariaTest('Up arrow key moves focus', 'grid/LayoutGrids.html', 'key-up-arrow', async (t) => { - - const cellSelectors = [ - ['ex1', '#ex1 [role="gridcell"]', 'a'], - ['ex2', '#ex2 [role="row"] [role="gridcell"]:first-of-type', 'a'], - ['ex3', '#ex3 [role="row"] [role="gridcell"]:first-child', 'a'] - ]; - - for (let [exId, selector, focusableElement] of cellSelectors) { - const ex = pageExamples[exId]; +); + +ariaTest( + 'Up arrow key moves focus', + 'grid/LayoutGrids.html', + 'key-up-arrow', + async (t) => { + const cellSelectors = [ + ['ex1', '#ex1 [role="gridcell"]', 'a'], + ['ex2', '#ex2 [role="row"] [role="gridcell"]:first-of-type', 'a'], + ['ex3', '#ex3 [role="row"] [role="gridcell"]:first-child', 'a'], + ]; + + for (let [exId, selector, focusableElement] of cellSelectors) { + const ex = pageExamples[exId]; - if (exId == 'ex3') { - // This test depends on the "page down" button which is not specified by - // the widget's description. It does this to avoid relying on behaviors - // that are tested elsewhere. - await clickUntilDisabled(t.context.session, '#ex3_pagedown_button'); - } + if (exId == 'ex3') { + // This test depends on the "page down" button which is not specified by + // the widget's description. It does this to avoid relying on behaviors + // that are tested elsewhere. + await clickUntilDisabled(t.context.session, '#ex3_pagedown_button'); + } - const gridcellElements = await t.context.queryElements(t, selector); - const lastCellIndex = gridcellElements.length - 1; + const gridcellElements = await t.context.queryElements(t, selector); + const lastCellIndex = gridcellElements.length - 1; - // Find the last focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[lastCellIndex], focusableElement); + // Find the last focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[lastCellIndex], + focusableElement + ); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the last gridcell: ' + selector); - } + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the last gridcell: ' + + selector + ); + } - // Test focus moves to previous row on up arrow + // Test focus moves to previous row on up arrow + + for (let index = gridcellElements.length - 2; index > -1; index--) { + await activeElement.sendKeys(Key.ARROW_UP); + const correctActiveElement = await checkActiveElement( + t, + selector, + index + ); + + t.true( + correctActiveElement, + 'Example ' + + exId + + ': gridcell `' + + (await gridcellElements[index].getText()) + + '` should receive focus after arrow up' + ); + + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); + } - for (let index = gridcellElements.length - 2; index > -1; index--) { + // Test up arrow on first row await activeElement.sendKeys(Key.ARROW_UP); - const correctActiveElement = await checkActiveElement(t, selector, index); - + const correctActiveElement = await checkActiveElement(t, selector, 0); t.true( correctActiveElement, - 'Example ' + exId + ': gridcell `' + (await gridcellElements[index].getText()) + '` should receive focus after arrow up' + 'Example ' + + exId + + ': Up Arrow sent to fist gridcell should not move focus.' ); - - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); } - - // Test up arrow on first row - - await activeElement.sendKeys(Key.ARROW_UP); - const correctActiveElement = await checkActiveElement(t, selector, 0); - t.true( - correctActiveElement, - 'Example ' + exId + ': Up Arrow sent to fist gridcell should not move focus.' - ); } -}); - -ariaTest('PageDown key moves focus', 'grid/LayoutGrids.html', 'key-page-down', async (t) => { - - const ex = pageExamples.ex3; - const cellSelectors = [ - ['first', '#ex3 [role="row"] [role="gridcell"]:nth-child(1)', 'a'], - ['second', '#ex3 [role="row"] [role="gridcell"]:nth-child(2)', 'gridcell'], - ['third', '#ex3 [role="row"] [role="gridcell"]:nth-child(3)', 'gridcell'] - ]; - const jumpBy = Number(await t.context.session - .findElement(By.css('#ex3 [role="grid"]')) - .getAttribute('data-per-page')); - - for (let [initialCell, selector, focusableElement] of cellSelectors) { +); + +ariaTest( + 'PageDown key moves focus', + 'grid/LayoutGrids.html', + 'key-page-down', + async (t) => { + const ex = pageExamples.ex3; + const cellSelectors = [ + ['first', '#ex3 [role="row"] [role="gridcell"]:nth-child(1)', 'a'], + [ + 'second', + '#ex3 [role="row"] [role="gridcell"]:nth-child(2)', + 'gridcell', + ], + ['third', '#ex3 [role="row"] [role="gridcell"]:nth-child(3)', 'gridcell'], + ]; + const jumpBy = Number( + await t.context.session + .findElement(By.css('#ex3 [role="grid"]')) + .getAttribute('data-per-page') + ); - await reload(t); + for (let [initialCell, selector, focusableElement] of cellSelectors) { + await reload(t); - let finalIndex; - const gridcellElements = (await t.context.queryElements(t, selector)); + let finalIndex; + const gridcellElements = await t.context.queryElements(t, selector); - // Find the first focusable element + // Find the first focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[0], focusableElement); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the first gridcell: ' + selector); - } + let activeElement = await focusOnOrInCell( + t, + gridcellElements[0], + focusableElement + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the first gridcell: ' + + selector + ); + } - // Test focus moves to next element on paging key + // Test focus moves to next element on paging key + + for ( + let index = jumpBy; + index < gridcellElements.length; + index += jumpBy + ) { + await activeElement.sendKeys(Key.PAGE_DOWN); + const correctActiveElement = await checkActiveElement( + t, + selector, + index + ); + + t.true( + correctActiveElement, + initialCell + + ' cell in row: gridcell `' + + (await gridcellElements[index].getText()) + + '` should receive focus after page down' + ); + + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); + finalIndex = index; + } - for (let index = jumpBy; index < gridcellElements.length; index += jumpBy) { + // Test paging key on final element await activeElement.sendKeys(Key.PAGE_DOWN); - const correctActiveElement = await checkActiveElement(t, selector, index); - + const correctActiveElement = await checkActiveElement( + t, + selector, + finalIndex + ); t.true( correctActiveElement, - initialCell + ' cell in row: gridcell `' + (await gridcellElements[index].getText()) + '` should receive focus after page down' + initialCell + + ' cell in row: Page Down sent to final gridcell should not move focus.' ); - - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - finalIndex = index; } - - // Test paging key on final element - - await activeElement.sendKeys(Key.PAGE_DOWN); - const correctActiveElement = await checkActiveElement(t, selector, finalIndex); - t.true( - correctActiveElement, - initialCell + ' cell in row: Page Down sent to final gridcell should not move focus.' - ); } -}); - -ariaTest('PageUp key moves focus', 'grid/LayoutGrids.html', 'key-page-up', async (t) => { - - const ex = pageExamples.ex3; - const cellSelectors = [ - ['first', '#ex3 [role="row"] [role="gridcell"]:nth-child(1)', 'a'], - ['second', '#ex3 [role="row"] [role="gridcell"]:nth-child(2)', 'gridcell'], - ['third', '#ex3 [role="row"] [role="gridcell"]:nth-child(3)', 'gridcell'] - ]; - const jumpBy = Number(await t.context.session - .findElement(By.css('#ex3 [role="grid"]')) - .getAttribute('data-per-page')); +); + +ariaTest( + 'PageUp key moves focus', + 'grid/LayoutGrids.html', + 'key-page-up', + async (t) => { + const ex = pageExamples.ex3; + const cellSelectors = [ + ['first', '#ex3 [role="row"] [role="gridcell"]:nth-child(1)', 'a'], + [ + 'second', + '#ex3 [role="row"] [role="gridcell"]:nth-child(2)', + 'gridcell', + ], + ['third', '#ex3 [role="row"] [role="gridcell"]:nth-child(3)', 'gridcell'], + ]; + const jumpBy = Number( + await t.context.session + .findElement(By.css('#ex3 [role="grid"]')) + .getAttribute('data-per-page') + ); - for (let [initialCell, selector, focusableElement] of cellSelectors) { + for (let [initialCell, selector, focusableElement] of cellSelectors) { + await reload(t); + // This test depends on the "page down" button which is not specified by + // the widget's description. It does this to avoid relying on behaviors + // that are tested elsewhere. + await clickUntilDisabled(t.context.session, '#ex3_pagedown_button'); - await reload(t); - // This test depends on the "page down" button which is not specified by - // the widget's description. It does this to avoid relying on behaviors - // that are tested elsewhere. - await clickUntilDisabled(t.context.session, '#ex3_pagedown_button'); + let finalIndex; + const gridcellElements = await t.context.queryElements(t, selector); - let finalIndex; - const gridcellElements = (await t.context.queryElements(t, selector)); + const lastCellIndex = gridcellElements.length - 1; - const lastCellIndex = gridcellElements.length - 1; + // Find the last focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[lastCellIndex], + focusableElement + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the final gridcell: ' + + selector + ); + } - // Find the last focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[lastCellIndex], focusableElement); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the final gridcell: ' + selector); - } + // Test focus moves to next element on paging key + + // The final "page" of rows may not contain the maximum number of rows. In + // this case, the first "Page Up" keypress will involve traversing fewer + // rows than subsequent key presses. + const finalPageLength = gridcellElements.length % jumpBy || jumpBy; + const penultimate = gridcellElements.length - 1 - finalPageLength; + for (let index = penultimate; index > -1; index -= jumpBy) { + await activeElement.sendKeys(Key.PAGE_UP); + const correctActiveElement = await checkActiveElement( + t, + selector, + index + ); + + t.true( + correctActiveElement, + initialCell + + ' cell in row: gridcell `' + + (await gridcellElements[index].getText()) + + '` should receive focus after page up' + ); + + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); + finalIndex = index; + } - // Test focus moves to next element on paging key + // Test paging key on final element - // The final "page" of rows may not contain the maximum number of rows. In - // this case, the first "Page Up" keypress will involve traversing fewer - // rows than subsequent key presses. - const finalPageLength = (gridcellElements.length % jumpBy) || jumpBy; - const penultimate = gridcellElements.length - 1 - finalPageLength; - for (let index = penultimate; index > -1; index -= jumpBy) { await activeElement.sendKeys(Key.PAGE_UP); - const correctActiveElement = await checkActiveElement(t, selector, index); - + const correctActiveElement = await checkActiveElement( + t, + selector, + finalIndex + ); t.true( correctActiveElement, - initialCell + ' cell in row: gridcell `' + (await gridcellElements[index].getText()) + '` should receive focus after page up' + initialCell + + ' cell in row: Page Up sent to first gridcell should not move focus.' ); - - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - finalIndex = index; } - - // Test paging key on final element - - await activeElement.sendKeys(Key.PAGE_UP); - const correctActiveElement = await checkActiveElement(t, selector, finalIndex); - t.true( - correctActiveElement, - initialCell + ' cell in row: Page Up sent to first gridcell should not move focus.' - ); } -}); - -ariaTest('Home key moves focus', 'grid/LayoutGrids.html', 'key-home', async (t) => { +); + +ariaTest( + 'Home key moves focus', + 'grid/LayoutGrids.html', + 'key-home', + async (t) => { + const firstElementInFirstRowText = { + ex1: 'ARIA 1.1 Specification', + ex2: 'Recipient Name 1', + ex3: 'WAI-ARIA Overview Web Accessibility Initiative W3C', + }; + + for (let [exId, ex] of Object.entries(pageExamples)) { + const gridcellElements = await t.context.queryElements( + t, + ex.gridcellSelector + ); - const firstElementInFirstRowText = { - ex1: 'ARIA 1.1 Specification', - ex2: 'Recipient Name 1', - ex3: 'WAI-ARIA Overview Web Accessibility Initiative W3C' - }; + // Find the first focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[0], + ex.focusableElements[0] + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the first gridcell: ' + + ex.gridcellSelector + ); + } - for (let [exId, ex] of Object.entries(pageExamples)) { + // Move the focused element off the first element in the row + await activeElement.sendKeys(Key.ARROW_RIGHT); + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); - const gridcellElements = await t.context.queryElements(t, ex.gridcellSelector); + // Test focus moves back to the first element in the row after sending key HOME + await activeElement.sendKeys(Key.HOME); + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); - // Find the first focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[0], ex.focusableElements[0]); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the first gridcell: ' + ex.gridcellSelector); + t.is( + await activeElement.getText(), + firstElementInFirstRowText[exId], + 'Example ' + + exId + + ': home should move focus to first element in the row' + ); } - - // Move the focused element off the first element in the row - await activeElement.sendKeys(Key.ARROW_RIGHT); - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - - // Test focus moves back to the first element in the row after sending key HOME - await activeElement.sendKeys(Key.HOME); - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - - t.is( - await activeElement.getText(), - firstElementInFirstRowText[exId], - 'Example ' + exId + ': home should move focus to first element in the row' - ); - } -}); - -ariaTest('End key moves focus', 'grid/LayoutGrids.html', 'key-end', async (t) => { - - const lastElementInFirstRowText = { - ex1: 'SVG 2 Specification', - ex2: 'X', - ex3: 'WAI-ARIA, the Accessible Rich Internet Applications Suite, defines a way to make Web content and Web applications more accessible to people with disabilities.' - }; +); + +ariaTest( + 'End key moves focus', + 'grid/LayoutGrids.html', + 'key-end', + async (t) => { + const lastElementInFirstRowText = { + ex1: 'SVG 2 Specification', + ex2: 'X', + ex3: + 'WAI-ARIA, the Accessible Rich Internet Applications Suite, defines a way to make Web content and Web applications more accessible to people with disabilities.', + }; + + for (let [exId, ex] of Object.entries(pageExamples)) { + const gridcellElements = await t.context.queryElements( + t, + ex.gridcellSelector + ); - for (let [exId, ex] of Object.entries(pageExamples)) { + // Find the first focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[0], + ex.focusableElements[0] + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the first gridcell: ' + + ex.gridcellSelector + ); + } - const gridcellElements = await t.context.queryElements(t, ex.gridcellSelector); + // Test focus to last element in row using key END + await activeElement.sendKeys(Key.END); + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); - // Find the first focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[0], ex.focusableElements[0]); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the first gridcell: ' + ex.gridcellSelector); + t.is( + await activeElement.getText(), + lastElementInFirstRowText[exId], + 'Example ' + exId + ': END should move focus to last element in the row' + ); } - - // Test focus to last element in row using key END - await activeElement.sendKeys(Key.END); - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - - t.is( - await activeElement.getText(), - lastElementInFirstRowText[exId], - 'Example ' + exId + ': END should move focus to last element in the row' - ); } -}); - -ariaTest('control+home keys moves focus', 'grid/LayoutGrids.html', 'key-control-home', async (t) => { +); + +ariaTest( + 'control+home keys moves focus', + 'grid/LayoutGrids.html', + 'key-control-home', + async (t) => { + const firstElementInFirstRowText = { + ex1: 'ARIA 1.1 Specification', + ex2: 'Recipient Name 1', + ex3: 'WAI-ARIA Overview Web Accessibility Initiative W3C', + }; + + for (let [exId, ex] of Object.entries(pageExamples)) { + const gridcellElements = await t.context.queryElements( + t, + ex.gridcellSelector + ); - const firstElementInFirstRowText = { - ex1: 'ARIA 1.1 Specification', - ex2: 'Recipient Name 1', - ex3: 'WAI-ARIA Overview Web Accessibility Initiative W3C' - }; + // Find the first focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[0], + ex.focusableElements[0] + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the first gridcell: ' + + ex.gridcellSelector + ); + } - for (let [exId, ex] of Object.entries(pageExamples)) { + // Move the focused element off the first element in the row + await activeElement.sendKeys(Key.ARROW_RIGHT); + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); - const gridcellElements = await t.context.queryElements(t, ex.gridcellSelector); + // Test focus to last element in row using key chord CONTROL+HOME + await activeElement.sendKeys(Key.chord(Key.CONTROL, Key.HOME)); + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); - // Find the first focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[0], ex.focusableElements[0]); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the first gridcell: ' + ex.gridcellSelector); + t.is( + await activeElement.getText(), + firstElementInFirstRowText[exId], + 'Example ' + + exId + + ': CONTROL+HOME should move focus to first element in the first row' + ); } - - // Move the focused element off the first element in the row - await activeElement.sendKeys(Key.ARROW_RIGHT); - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - - // Test focus to last element in row using key chord CONTROL+HOME - await activeElement.sendKeys(Key.chord(Key.CONTROL, Key.HOME)); - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - - t.is( - await activeElement.getText(), - firstElementInFirstRowText[exId], - 'Example ' + exId + ': CONTROL+HOME should move focus to first element in the first row' - ); } -}); - - -ariaTest('Control+end keys moves focus', 'grid/LayoutGrids.html', 'key-control-end', async (t) => { - - const lastElementInFirstRowText = { - ex1: 'SVG 2 Specification', - ex2: 'X', - ex3: 'Jan 3, 2014 - NVDA 2 supports all landmarks except it will not support navigation to “application”; Window Eyes as of V.7 does not support ARIA landmarks.' - }; +); + +ariaTest( + 'Control+end keys moves focus', + 'grid/LayoutGrids.html', + 'key-control-end', + async (t) => { + const lastElementInFirstRowText = { + ex1: 'SVG 2 Specification', + ex2: 'X', + ex3: + 'Jan 3, 2014 - NVDA 2 supports all landmarks except it will not support navigation to “application”; Window Eyes as of V.7 does not support ARIA landmarks.', + }; + + for (let [exId, ex] of Object.entries(pageExamples)) { + const gridcellElements = await t.context.queryElements( + t, + ex.gridcellSelector + ); - for (let [exId, ex] of Object.entries(pageExamples)) { + // Find the first focusable element + let activeElement = await focusOnOrInCell( + t, + gridcellElements[0], + ex.focusableElements[0] + ); + if (!activeElement) { + throw new Error( + 'Could not focus on element or any descendent in the first gridcell: ' + + ex.gridcellSelector + ); + } - const gridcellElements = await t.context.queryElements(t, ex.gridcellSelector); + // Test focus to last element in row using key CONTROL+END + await activeElement.sendKeys(Key.chord(Key.CONTROL, Key.END)); + activeElement = await t.context.session.executeScript(() => { + return document.activeElement; + }); - // Find the first focusable element - let activeElement = await focusOnOrInCell(t, gridcellElements[0], ex.focusableElements[0]); - if (!activeElement) { - throw new Error('Could not focus on element or any descendent in the first gridcell: ' + ex.gridcellSelector); + t.is( + await activeElement.getText(), + lastElementInFirstRowText[exId], + 'Example ' + + exId + + ': CONTROL+END should move focus to last element in the last row' + ); } - - // Test focus to last element in row using key CONTROL+END - await activeElement.sendKeys(Key.chord(Key.CONTROL, Key.END)); - activeElement = await t.context.session.executeScript(() => { - return document.activeElement; - }); - - t.is( - await activeElement.getText(), - lastElementInFirstRowText[exId], - 'Example ' + exId + ': CONTROL+END should move focus to last element in the last row' - ); } -}); - +); diff --git a/test/tests/grid_dataGrids.js b/test/tests/grid_dataGrids.js index ed86f17043..5f0ddc35c3 100644 --- a/test/tests/grid_dataGrids.js +++ b/test/tests/grid_dataGrids.js @@ -14,7 +14,7 @@ const ex = { rowSelector: '#ex1 tr', lastColumn: 5, lastRow: 7, - firstInteractiveRow: 2 + firstInteractiveRow: 2, }, 2: { gridSelector: '#ex2 [role="grid"]', @@ -23,7 +23,7 @@ const ex = { amountHeaderSelector: '#ex2 tr:nth-of-type(1) th:nth-of-type(5)', lastColumn: 6, lastRow: 8, - firstInteractiveRow: 1 + firstInteractiveRow: 1, }, 3: { gridSelector: '#ex3 [role="grid"]', @@ -31,49 +31,80 @@ const ex = { hideButtonSelector: '#ex3 #toggle_column_btn', lastColumn: 6, lastRow: 16, - firstInteractiveRow: 2 - } + firstInteractiveRow: 2, + }, }; -const checkFocusOnOrInCell = async function (t, gridSelector, rowIndex, columnIndex) { - return t.context.session.executeScript(function () { - const [gridSelector, rowIndex, columnIndex] = arguments; - - let selector = gridSelector + ' tr:nth-of-type(' + rowIndex + - ') td:nth-of-type(' + columnIndex + ')'; - - // If rowIndex === 1, then focus will be on a th element - if (rowIndex === 1) { - selector = gridSelector + ' tr:nth-of-type(' + rowIndex + - ') th:nth-of-type(' + columnIndex + ')'; - } - - // If the element has "tabindex", it is the candidate for focus - const cellElement = document.querySelector(selector); - if (cellElement.hasAttribute('tabindex')) { - return document.activeElement === cellElement; - } +const checkFocusOnOrInCell = async function ( + t, + gridSelector, + rowIndex, + columnIndex +) { + return t.context.session.executeScript( + function () { + const [gridSelector, rowIndex, columnIndex] = arguments; + + let selector = + gridSelector + + ' tr:nth-of-type(' + + rowIndex + + ') td:nth-of-type(' + + columnIndex + + ')'; + + // If rowIndex === 1, then focus will be on a th element + if (rowIndex === 1) { + selector = + gridSelector + + ' tr:nth-of-type(' + + rowIndex + + ') th:nth-of-type(' + + columnIndex + + ')'; + } - // Look for an interactive element in the gridcell to find candidate for focus - const interactiveElement = cellElement.querySelector('[tabindex]'); - return document.activeElement === interactiveElement; + // If the element has "tabindex", it is the candidate for focus + const cellElement = document.querySelector(selector); + if (cellElement.hasAttribute('tabindex')) { + return document.activeElement === cellElement; + } - }, gridSelector, rowIndex, columnIndex); + // Look for an interactive element in the gridcell to find candidate for focus + const interactiveElement = cellElement.querySelector('[tabindex]'); + return document.activeElement === interactiveElement; + }, + gridSelector, + rowIndex, + columnIndex + ); }; -const sendKeyToGridcell = async function (t, gridSelector, rowIndex, columnIndex, key) { - - let selector = gridSelector + ' tr:nth-of-type(' + rowIndex + - ') td:nth-of-type(' + columnIndex + ')'; +const sendKeyToGridcell = async function ( + t, + gridSelector, + rowIndex, + columnIndex, + key +) { + let selector = + gridSelector + + ' tr:nth-of-type(' + + rowIndex + + ') td:nth-of-type(' + + columnIndex + + ')'; // If the element has "tabindex", send KEY here const cellElement = await t.context.session.findElement(By.css(selector)); - if (await cellElement.getAttribute('tabindex') !== null) { + if ((await cellElement.getAttribute('tabindex')) !== null) { return await cellElement.sendKeys(key); } // Look for an interactive element in the gridcell to send KEY - const interactiveElement = await cellElement.findElement(By.css('[tabindex]')); + const interactiveElement = await cellElement.findElement( + By.css('[tabindex]') + ); return await interactiveElement.sendKeys(key); }; @@ -82,504 +113,673 @@ const scrollToEndOfExample3 = async function (t) { await sendKeyToGridcell(t, ex[3].gridSelector, 7, 1, Key.PAGE_DOWN); }; - // Attributes ariaTest('Test for role="grid"', exampleFile, 'grid-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'grid', '1', 'table'); + await assertAriaRoles(t, 'ex1', 'grid', '1', 'table'); await assertAriaRoles(t, 'ex2', 'grid', '1', 'table'); await assertAriaRoles(t, 'ex3', 'grid', '1', 'table'); }); -ariaTest('aria-labelledby attribute on all examples', exampleFile, 'aria-labelledby', async (t) => { +ariaTest( + 'aria-labelledby attribute on all examples', + exampleFile, + 'aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex[1].gridSelector); - await assertAriaLabelledby(t, ex[2].gridSelector); - await assertAriaLabelledby(t, ex[3].gridSelector); -}); + await assertAriaLabelledby(t, ex[2].gridSelector); + await assertAriaLabelledby(t, ex[3].gridSelector); + } +); -ariaTest('aria-rowcount attribute in example 3', exampleFile, 'aria-rowcount', async (t) => { +ariaTest( + 'aria-rowcount attribute in example 3', + exampleFile, + 'aria-rowcount', + async (t) => { await assertAttributeValues(t, ex[3].gridSelector, 'aria-rowcount', '16'); -}); + } +); -ariaTest('aria-colcount attribute in example 3', exampleFile, 'aria-colcount', async (t) => { +ariaTest( + 'aria-colcount attribute in example 3', + exampleFile, + 'aria-colcount', + async (t) => { await assertAttributeValues(t, ex[3].gridSelector, 'aria-colcount', '6'); -}); - -ariaTest('aria-rowindex attribute in example 3', exampleFile, 'aria-rowindex', async (t) => { - - let rows = await t.context.queryElements(t, ex[3].rowSelector); - - let index = 1; - for (let row of rows) { - t.is( - await row.getAttribute('aria-rowindex'), - index.toString(), - 'In example 3, tr element at index ' + index + ' should have aria-rowindex value: ' + index - ); - index++; } -}); - -ariaTest('aria-colindex attribute in example 3', exampleFile, 'aria-colindex', async (t) => { +); - let rows = await t.context.queryElements(t, ex[3].rowSelector); +ariaTest( + 'aria-rowindex attribute in example 3', + exampleFile, + 'aria-rowindex', + async (t) => { + let rows = await t.context.queryElements(t, ex[3].rowSelector); - // Check all the headers - let items = await t.context.queryElements(t, 'th', rows.shift()); - let index = 1; - for (let item of items) { - t.is( - await item.getAttribute('aria-colindex'), - index.toString(), - 'In example 3, th element at column index ' + index + ' should have aria-colindex value: ' + index - ); - index++; + let index = 1; + for (let row of rows) { + t.is( + await row.getAttribute('aria-rowindex'), + index.toString(), + 'In example 3, tr element at index ' + + index + + ' should have aria-rowindex value: ' + + index + ); + index++; + } } +); - // Check all the grid items - for (let row of rows) { - let items = await t.context.queryElements(t, 'td', row); +ariaTest( + 'aria-colindex attribute in example 3', + exampleFile, + 'aria-colindex', + async (t) => { + let rows = await t.context.queryElements(t, ex[3].rowSelector); + // Check all the headers + let items = await t.context.queryElements(t, 'th', rows.shift()); let index = 1; for (let item of items) { t.is( await item.getAttribute('aria-colindex'), index.toString(), - 'In example 3, td elements at column index ' + index + ' should have aria-colindex value: ' + index + 'In example 3, th element at column index ' + + index + + ' should have aria-colindex value: ' + + index ); index++; } - } -}); - -ariaTest('aria-sort set appropriately in example 2', exampleFile, 'aria-sort', async (t) => { - - let dateHeader = await t.context.session.findElement(By.css(ex[2].dateHeaderSelector)); - let dateButton = dateHeader.findElement(By.css('[role="button"]')); - let amountHeader = await t.context.session.findElement(By.css(ex[2].amountHeaderSelector)); - let amountButton = amountHeader.findElement(By.css('[role="button"]')); + // Check all the grid items + for (let row of rows) { + let items = await t.context.queryElements(t, 'td', row); + + let index = 1; + for (let item of items) { + t.is( + await item.getAttribute('aria-colindex'), + index.toString(), + 'In example 3, td elements at column index ' + + index + + ' should have aria-colindex value: ' + + index + ); + index++; + } + } + } +); + +ariaTest( + 'aria-sort set appropriately in example 2', + exampleFile, + 'aria-sort', + async (t) => { + let dateHeader = await t.context.session.findElement( + By.css(ex[2].dateHeaderSelector) + ); + let dateButton = dateHeader.findElement(By.css('[role="button"]')); - // By default, the rows are sorted by date ascending - t.is( - await dateHeader.getAttribute('aria-sort'), - 'ascending', - 'On page load, date header should reflect rows are sorted date ascending', - ); - t.is( - await amountHeader.getAttribute('aria-sort'), - 'none', - 'On page load, amount header should reflect rows are not sorted by amount', - ); + let amountHeader = await t.context.session.findElement( + By.css(ex[2].amountHeaderSelector) + ); + let amountButton = amountHeader.findElement(By.css('[role="button"]')); - await dateButton.click(); + // By default, the rows are sorted by date ascending + t.is( + await dateHeader.getAttribute('aria-sort'), + 'ascending', + 'On page load, date header should reflect rows are sorted date ascending' + ); + t.is( + await amountHeader.getAttribute('aria-sort'), + 'none', + 'On page load, amount header should reflect rows are not sorted by amount' + ); - t.is( - await dateHeader.getAttribute('aria-sort'), - 'descending', - 'After clicking date header, date header should reflect rows are sorted date descending', - ); - t.is( - await amountHeader.getAttribute('aria-sort'), - 'none', - 'After clicking date header, amount header should reflect rows are not sorted by amount', - ); + await dateButton.click(); - await amountButton.click(); + t.is( + await dateHeader.getAttribute('aria-sort'), + 'descending', + 'After clicking date header, date header should reflect rows are sorted date descending' + ); + t.is( + await amountHeader.getAttribute('aria-sort'), + 'none', + 'After clicking date header, amount header should reflect rows are not sorted by amount' + ); - t.is( - await dateHeader.getAttribute('aria-sort'), - 'none', - 'After clicking amount header, date header should reflect rows are not sorted by date', - ); - t.is( - await amountHeader.getAttribute('aria-sort'), - 'ascending', - 'After clicking amount header, amount header should reflect rows are sorted by amount ascending', - ); + await amountButton.click(); - await amountButton.click(); + t.is( + await dateHeader.getAttribute('aria-sort'), + 'none', + 'After clicking amount header, date header should reflect rows are not sorted by date' + ); + t.is( + await amountHeader.getAttribute('aria-sort'), + 'ascending', + 'After clicking amount header, amount header should reflect rows are sorted by amount ascending' + ); - t.is( - await dateHeader.getAttribute('aria-sort'), - 'none', - 'After clicking amount header twice, date header should reflect rows are not sorted by date', - ); - t.is( - await amountHeader.getAttribute('aria-sort'), - 'descending', - 'After clicking amount header twice, amount header should reflect rows are sorted by amount descending', - ); -}); + await amountButton.click(); + t.is( + await dateHeader.getAttribute('aria-sort'), + 'none', + 'After clicking amount header twice, date header should reflect rows are not sorted by date' + ); + t.is( + await amountHeader.getAttribute('aria-sort'), + 'descending', + 'After clicking amount header twice, amount header should reflect rows are sorted by amount descending' + ); + } +); // Keys -ariaTest('Right arrow moves focus, example 1 and 2', exampleFile, 'key-right-arrow', async (t) => { - - // Examples 1 and 2 - for (let example of [1,2]) { - const gridSelector = ex[example].gridSelector; - const lastColumn = ex[example].lastColumn; - const lastRow = ex[example].lastRow; +ariaTest( + 'Right arrow moves focus, example 1 and 2', + exampleFile, + 'key-right-arrow', + async (t) => { + // Examples 1 and 2 + for (let example of [1, 2]) { + const gridSelector = ex[example].gridSelector; + const lastColumn = ex[example].lastColumn; + const lastRow = ex[example].lastRow; + + // Index starts at 2 to skip header + for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // Test focus moves right + await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_RIGHT); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 2), + 'After sending ARROW RIGHT to element at column index 1 in row ' + + rowIndex + + ' focus should be on element at column index 2' + ); + + await sendKeyToGridcell(t, gridSelector, rowIndex, 2, Key.ARROW_RIGHT); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3), + 'After sending ARROW RIGHT to element at column index 2 in row ' + + rowIndex + + ' focus should be on element at column index 3' + ); + + // Test focus does not wrap + await sendKeyToGridcell( + t, + gridSelector, + rowIndex, + lastColumn, + Key.ARROW_RIGHT + ); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, lastColumn), + 'After sending ARROW RIGHT to the last item in row ' + + rowIndex + + ' focus should remain on the last item (' + + lastColumn + + ')' + ); + } + } + } +); + +ariaTest( + 'Right arrow moves focus, example 3', + exampleFile, + 'key-right-arrow', + async (t) => { + let gridSelector = ex[3].gridSelector; + let lastColumn = ex[3].lastColumn; + let lastRow = ex[3].lastRow; + let rowSelector = ex[3].rowSelector; // Index starts at 2 to skip header for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // If the row is not displayed, send page down to the last rows first element + let rows = await t.context.queryElements(t, rowSelector); + if (!(await rows[rowIndex - 1].isDisplayed())) { + let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); + await previousRowCell.sendKeys(Key.PAGE_DOWN); + } // Test focus moves right await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_RIGHT); t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 2,), - 'After sending ARROW RIGHT to element at column index 1 in row ' + rowIndex + ' focus should be on element at column index 2' + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 2), + 'After sending ARROW RIGHT to element at column index 1 in row ' + + rowIndex + + ' focus should be on element at column index 2' ); await sendKeyToGridcell(t, gridSelector, rowIndex, 2, Key.ARROW_RIGHT); t.true( await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3), - 'After sending ARROW RIGHT to element at column index 2 in row ' + rowIndex + ' focus should be on element at column index 3' + 'After sending ARROW RIGHT to element at column index 2 in row ' + + rowIndex + + ' focus should be on element at column index 3' ); // Test focus does not wrap - await sendKeyToGridcell(t, gridSelector, rowIndex, lastColumn, Key.ARROW_RIGHT); + await sendKeyToGridcell( + t, + gridSelector, + rowIndex, + lastColumn, + Key.ARROW_RIGHT + ); t.true( await checkFocusOnOrInCell(t, gridSelector, rowIndex, lastColumn), - 'After sending ARROW RIGHT to the last item in row ' + rowIndex + ' focus should remain on the last item (' + lastColumn + ')' + 'After sending ARROW RIGHT to the last item in row ' + + rowIndex + + ' focus should remain on the last item (' + + lastColumn + + ')' ); } - } -}); - -ariaTest('Right arrow moves focus, example 3', exampleFile, 'key-right-arrow', async (t) => { - - let gridSelector = ex[3].gridSelector; - let lastColumn = ex[3].lastColumn; - let lastRow = ex[3].lastRow; - let rowSelector = ex[3].rowSelector; - - // Index starts at 2 to skip header - for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { - - // If the row is not displayed, send page down to the last rows first element - let rows = (await t.context.queryElements(t, rowSelector)); - if (!(await rows[rowIndex - 1].isDisplayed())) { - let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); - await previousRowCell.sendKeys(Key.PAGE_DOWN); - } - // Test focus moves right - await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_RIGHT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 2,), - 'After sending ARROW RIGHT to element at column index 1 in row ' + rowIndex + ' focus should be on element at column index 2' - ); + await t.context.session.get(t.context.url); - await sendKeyToGridcell(t, gridSelector, rowIndex, 2, Key.ARROW_RIGHT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3), - 'After sending ARROW RIGHT to element at column index 2 in row ' + rowIndex + ' focus should be on element at column index 3' - ); + // click the hide columns button + await t.context.session + .findElement(By.css(ex[3].hideButtonSelector)) + .click(); - // Test focus does not wrap - await sendKeyToGridcell(t, gridSelector, rowIndex, lastColumn, Key.ARROW_RIGHT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, lastColumn), - 'After sending ARROW RIGHT to the last item in row ' + rowIndex + ' focus should remain on the last item (' + lastColumn + ')' - ); - } - - await t.context.session.get(t.context.url); + // Index starts at 2 to skip header + for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // If the row is not displayed, send page down to the last rows first element + let rows = await t.context.queryElements(t, rowSelector); + if (!(await rows[rowIndex - 1].isDisplayed())) { + let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); + await previousRowCell.sendKeys(Key.PAGE_DOWN); + } - // click the hide columns button - await t.context.session.findElement(By.css(ex[3].hideButtonSelector)).click(); + // Test focus moves right + await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_RIGHT); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3), + 'After sending ARROW RIGHT to element at column index 1 in row ' + + rowIndex + + ' focus should be on element at column index 2' + ); - // Index starts at 2 to skip header - for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + await sendKeyToGridcell(t, gridSelector, rowIndex, 3, Key.ARROW_RIGHT); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 5), + 'After sending ARROW RIGHT to element at column index 2 in row ' + + rowIndex + + ' focus should be on element at column index 3' + ); - // If the row is not displayed, send page down to the last rows first element - let rows = (await t.context.queryElements(t, rowSelector)); - if (!(await rows[rowIndex - 1].isDisplayed())) { - let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); - await previousRowCell.sendKeys(Key.PAGE_DOWN); + // Test focus does not wrap + await sendKeyToGridcell( + t, + gridSelector, + rowIndex, + lastColumn, + Key.ARROW_RIGHT + ); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, lastColumn), + 'After sending ARROW RIGHT to the last item in row ' + + rowIndex + + ' focus should remain on the last item (' + + lastColumn + + ')' + ); } - - // Test focus moves right - await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_RIGHT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3,), - 'After sending ARROW RIGHT to element at column index 1 in row ' + rowIndex + ' focus should be on element at column index 2' - ); - - await sendKeyToGridcell(t, gridSelector, rowIndex, 3, Key.ARROW_RIGHT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 5), - 'After sending ARROW RIGHT to element at column index 2 in row ' + rowIndex + ' focus should be on element at column index 3' - ); - - // Test focus does not wrap - await sendKeyToGridcell(t, gridSelector, rowIndex, lastColumn, Key.ARROW_RIGHT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, lastColumn), - 'After sending ARROW RIGHT to the last item in row ' + rowIndex + ' focus should remain on the last item (' + lastColumn + ')' - ); } -}); - -ariaTest('Left arrow moves focus, example 1 and 2', exampleFile, 'key-left-arrow', async (t) => { - - // Examples 1 and 2 - for (let example of [1,2]) { - const gridSelector = ex[example].gridSelector; - const lastColumn = ex[example].lastColumn; - const lastRow = ex[example].lastRow; +); + +ariaTest( + 'Left arrow moves focus, example 1 and 2', + exampleFile, + 'key-left-arrow', + async (t) => { + // Examples 1 and 2 + for (let example of [1, 2]) { + const gridSelector = ex[example].gridSelector; + const lastColumn = ex[example].lastColumn; + const lastRow = ex[example].lastRow; + + // Index starts at 2 to skip header + for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // Test focus moves left + await sendKeyToGridcell(t, gridSelector, rowIndex, 4, Key.ARROW_LEFT); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3), + 'After sending ARROW LEFT to element at column index 4 in row ' + + rowIndex + + ' focus should be on element at column index 3' + ); + + await sendKeyToGridcell(t, gridSelector, rowIndex, 3, Key.ARROW_LEFT); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 2), + 'After sending ARROW LEFT to element at column index 3 in row ' + + rowIndex + + ' focus should be on element at column index 2' + ); + + // Test focus does not wrap + await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_LEFT); + t.true( + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), + 'After sending ARROW LEFT to the first item in row ' + + rowIndex + + ' focus should remain on the first item' + ); + } + } + } +); + +ariaTest( + 'left arrow moves focus, example 3', + exampleFile, + 'key-left-arrow', + async (t) => { + let gridSelector = ex[3].gridSelector; + let lastColumn = ex[3].lastColumn; + let lastRow = ex[3].lastRow; + let rowSelector = ex[3].rowSelector; // Index starts at 2 to skip header for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // If the row is not displayed, send page down to the last rows first element + let rows = await t.context.queryElements(t, rowSelector); + if (!(await rows[rowIndex - 1].isDisplayed())) { + let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); + await previousRowCell.sendKeys(Key.PAGE_DOWN); + } // Test focus moves left await sendKeyToGridcell(t, gridSelector, rowIndex, 4, Key.ARROW_LEFT); t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3,), - 'After sending ARROW LEFT to element at column index 4 in row ' + rowIndex + ' focus should be on element at column index 3' + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3), + 'After sending ARROW LEFT to element at column index 4 in row ' + + rowIndex + + ' focus should be on element at column index 3' ); await sendKeyToGridcell(t, gridSelector, rowIndex, 3, Key.ARROW_LEFT); t.true( await checkFocusOnOrInCell(t, gridSelector, rowIndex, 2), - 'After sending ARROW LEFT to element at column index 3 in row ' + rowIndex + ' focus should be on element at column index 2' + 'After sending ARROW LEFT to element at column index 3 in row ' + + rowIndex + + ' focus should be on element at column index 2' ); // Test focus does not wrap await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_LEFT); t.true( await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), - 'After sending ARROW LEFT to the first item in row ' + rowIndex + ' focus should remain on the first item' + 'After sending ARROW LEFT to the first item in row ' + + rowIndex + + ' focus should remain on the first item' ); } - } -}); - -ariaTest('left arrow moves focus, example 3', exampleFile, 'key-left-arrow', async (t) => { - - let gridSelector = ex[3].gridSelector; - let lastColumn = ex[3].lastColumn; - let lastRow = ex[3].lastRow; - let rowSelector = ex[3].rowSelector; - - // Index starts at 2 to skip header - for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { - - // If the row is not displayed, send page down to the last rows first element - let rows = (await t.context.queryElements(t, rowSelector)); - if (!(await rows[rowIndex - 1].isDisplayed())) { - let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); - await previousRowCell.sendKeys(Key.PAGE_DOWN); - } - - // Test focus moves left - await sendKeyToGridcell(t, gridSelector, rowIndex, 4, Key.ARROW_LEFT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3,), - 'After sending ARROW LEFT to element at column index 4 in row ' + rowIndex + ' focus should be on element at column index 3' - ); - - await sendKeyToGridcell(t, gridSelector, rowIndex, 3, Key.ARROW_LEFT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 2), - 'After sending ARROW LEFT to element at column index 3 in row ' + rowIndex + ' focus should be on element at column index 2' - ); - - // Test focus does not wrap - await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_LEFT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), - 'After sending ARROW LEFT to the first item in row ' + rowIndex + ' focus should remain on the first item' - ); - } - - await t.context.session.get(t.context.url); - - // click the hide columns button - await t.context.session.findElement(By.css(ex[3].hideButtonSelector)).click(); - - // Index starts at 2 to skip header - for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { - - // If the row is not displayed, send page down to the last rows first element - let rows = (await t.context.queryElements(t, rowSelector)); - if (!(await rows[rowIndex - 1].isDisplayed())) { - let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); - await previousRowCell.sendKeys(Key.PAGE_DOWN); - } - - // Test focus moves left (skipping invisible columns) - await sendKeyToGridcell(t, gridSelector, rowIndex, 5, Key.ARROW_LEFT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3,), - 'After sending ARROW LEFT to element at column index 5 in row ' + rowIndex + ' focus should be on element at column index 3 (skipping invisible columns)' - ); - await sendKeyToGridcell(t, gridSelector, rowIndex, 3, Key.ARROW_LEFT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), - 'After sending ARROW LEFT to element at column index 3 in row ' + rowIndex + ' focus should be on element at column index 1 (skipping invisible columns)' - ); + await t.context.session.get(t.context.url); - // Test focus does not wrap - await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_LEFT); - t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), - 'After sending ARROW LEFT to the first item in row ' + rowIndex + ' focus should remain on the first item' - ); - } -}); - -ariaTest('Key down moves focus, examples 1,2,3', exampleFile, 'key-down-arrow', async (t) => { - - // Examples 1 and 2 and 3 - for (let example of [1,2,3]) { - const gridSelector = ex[example].gridSelector; - const lastColumn = ex[example].lastColumn; - const lastRow = ex[example].lastRow; - - let columnIndex = 1; + // click the hide columns button + await t.context.session + .findElement(By.css(ex[3].hideButtonSelector)) + .click(); // Index starts at 2 to skip header - for (let rowIndex = 2; rowIndex <= lastRow - 1; rowIndex++) { + for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // If the row is not displayed, send page down to the last rows first element + let rows = await t.context.queryElements(t, rowSelector); + if (!(await rows[rowIndex - 1].isDisplayed())) { + let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); + await previousRowCell.sendKeys(Key.PAGE_DOWN); + } - // Test focus moves down - await sendKeyToGridcell(t, gridSelector, rowIndex, columnIndex, Key.ARROW_DOWN); + // Test focus moves left (skipping invisible columns) + await sendKeyToGridcell(t, gridSelector, rowIndex, 5, Key.ARROW_LEFT); t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex + 1, columnIndex), - 'After sending ARROW DOWN to element in row ' + rowIndex + ' at column ' + columnIndex + ' focus should be on element in row ' + (rowIndex + 1) + ' at column ' + columnIndex + ' in example: ' + example + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 3), + 'After sending ARROW LEFT to element at column index 5 in row ' + + rowIndex + + ' focus should be on element at column index 3 (skipping invisible columns)' ); - // Switch the column every time - columnIndex++; - if (columnIndex > lastColumn) { - columnIndex = 1; - } - } - - // Test focus does not move on last row - await sendKeyToGridcell(t, gridSelector, lastRow, 1, Key.ARROW_DOWN); - t.true( - await checkFocusOnOrInCell(t, gridSelector, lastRow, 1), - 'After sending ARROW DOWN to element the last row (' + lastRow + ') at column 1 the focus should not move in example: ' + example - ); - } -}); - -ariaTest('Key up moves focus, examples 1,2,3', exampleFile, 'key-up-arrow', async (t) => { - - // Examples 1 and 2 and 3 - for (let example of [1,2,3]) { - const gridSelector = ex[example].gridSelector; - const lastColumn = ex[example].lastColumn; - const lastRow = ex[example].lastRow; - - if (example === 3) { - await scrollToEndOfExample3(t); - } - - let columnIndex = 1; - - // Index starts at 2 to skip header - for (let rowIndex = lastRow; rowIndex > 2; rowIndex--) { - - // Test focus moves down - await sendKeyToGridcell(t, gridSelector, rowIndex, columnIndex, Key.ARROW_UP); + await sendKeyToGridcell(t, gridSelector, rowIndex, 3, Key.ARROW_LEFT); t.true( - await checkFocusOnOrInCell(t, gridSelector, rowIndex - 1, columnIndex), - 'After sending ARROW DOWN to element in row ' + rowIndex + ' at column ' + columnIndex + ' focus should be on element in row ' + (rowIndex - 1) + ' at column ' + columnIndex + ' in example: ' + example + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), + 'After sending ARROW LEFT to element at column index 3 in row ' + + rowIndex + + ' focus should be on element at column index 1 (skipping invisible columns)' ); - // Switch the column every time - columnIndex++; - if (columnIndex > lastColumn) { - columnIndex = 1; - } - } - - // Test focus does not move on first row - if (example === 1 || example === 3) { - await sendKeyToGridcell(t, gridSelector, 2, 1, Key.ARROW_UP); + // Test focus does not wrap + await sendKeyToGridcell(t, gridSelector, rowIndex, 1, Key.ARROW_LEFT); t.true( - await checkFocusOnOrInCell(t, gridSelector, 2, 1), - 'After sending ARROW UP to element the first data row (2) at column 1 the focus should not move in example: ' + example + await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), + 'After sending ARROW LEFT to the first item in row ' + + rowIndex + + ' focus should remain on the first item' ); } + } +); + +ariaTest( + 'Key down moves focus, examples 1,2,3', + exampleFile, + 'key-down-arrow', + async (t) => { + // Examples 1 and 2 and 3 + for (let example of [1, 2, 3]) { + const gridSelector = ex[example].gridSelector; + const lastColumn = ex[example].lastColumn; + const lastRow = ex[example].lastRow; + + let columnIndex = 1; + + // Index starts at 2 to skip header + for (let rowIndex = 2; rowIndex <= lastRow - 1; rowIndex++) { + // Test focus moves down + await sendKeyToGridcell( + t, + gridSelector, + rowIndex, + columnIndex, + Key.ARROW_DOWN + ); + t.true( + await checkFocusOnOrInCell( + t, + gridSelector, + rowIndex + 1, + columnIndex + ), + 'After sending ARROW DOWN to element in row ' + + rowIndex + + ' at column ' + + columnIndex + + ' focus should be on element in row ' + + (rowIndex + 1) + + ' at column ' + + columnIndex + + ' in example: ' + + example + ); + + // Switch the column every time + columnIndex++; + if (columnIndex > lastColumn) { + columnIndex = 1; + } + } - // In example 2, focus moves to the header - if (example === 2) { - await sendKeyToGridcell(t, gridSelector, 2, 1, Key.ARROW_UP); + // Test focus does not move on last row + await sendKeyToGridcell(t, gridSelector, lastRow, 1, Key.ARROW_DOWN); t.true( - await checkFocusOnOrInCell(t, gridSelector, 1, 1), - 'After sending ARROW UP to element the first data row (2) at column 1 the focus should move to the header row (which is interactive) in example: ' + example + await checkFocusOnOrInCell(t, gridSelector, lastRow, 1), + 'After sending ARROW DOWN to element the last row (' + + lastRow + + ') at column 1 the focus should not move in example: ' + + example ); } - } -}); +); + +ariaTest( + 'Key up moves focus, examples 1,2,3', + exampleFile, + 'key-up-arrow', + async (t) => { + // Examples 1 and 2 and 3 + for (let example of [1, 2, 3]) { + const gridSelector = ex[example].gridSelector; + const lastColumn = ex[example].lastColumn; + const lastRow = ex[example].lastRow; -ariaTest('Page down moves focus in example 3', exampleFile, 'key-page-down', async (t) => { - t.pass(3); + if (example === 3) { + await scrollToEndOfExample3(t); + } - const gridSelector = ex[3].gridSelector; + let columnIndex = 1; + + // Index starts at 2 to skip header + for (let rowIndex = lastRow; rowIndex > 2; rowIndex--) { + // Test focus moves down + await sendKeyToGridcell( + t, + gridSelector, + rowIndex, + columnIndex, + Key.ARROW_UP + ); + t.true( + await checkFocusOnOrInCell( + t, + gridSelector, + rowIndex - 1, + columnIndex + ), + 'After sending ARROW DOWN to element in row ' + + rowIndex + + ' at column ' + + columnIndex + + ' focus should be on element in row ' + + (rowIndex - 1) + + ' at column ' + + columnIndex + + ' in example: ' + + example + ); + + // Switch the column every time + columnIndex++; + if (columnIndex > lastColumn) { + columnIndex = 1; + } + } - // Test focus moves focus and reveal hidden rows - await sendKeyToGridcell(t, gridSelector, 2, 1, Key.PAGE_DOWN); - t.true( - await checkFocusOnOrInCell(t, gridSelector, 7, 1), - 'After sending PAGE DOWN to element at row 2 column 1, the focus should be on row 7, column 1' - ); + // Test focus does not move on first row + if (example === 1 || example === 3) { + await sendKeyToGridcell(t, gridSelector, 2, 1, Key.ARROW_UP); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 2, 1), + 'After sending ARROW UP to element the first data row (2) at column 1 the focus should not move in example: ' + + example + ); + } - await sendKeyToGridcell(t, gridSelector, 7, 3, Key.PAGE_DOWN); - t.true( - await checkFocusOnOrInCell(t, gridSelector, 12, 3), - 'After sending PAGE DOWN to element at row 7 column 3, the focus should be on row 12, column 3' - ); + // In example 2, focus moves to the header + if (example === 2) { + await sendKeyToGridcell(t, gridSelector, 2, 1, Key.ARROW_UP); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 1, 1), + 'After sending ARROW UP to element the first data row (2) at column 1 the focus should move to the header row (which is interactive) in example: ' + + example + ); + } + } + } +); - // Test focus does not wrap - await sendKeyToGridcell(t, gridSelector, 12, 4, Key.PAGE_DOWN); - t.true( - await checkFocusOnOrInCell(t, gridSelector, 12, 4), - 'After sending PAGE DOWN to element at row 7 column 4, the focus should note move, because there are no more hidden rows' - ); +ariaTest( + 'Page down moves focus in example 3', + exampleFile, + 'key-page-down', + async (t) => { + t.pass(3); -}); + const gridSelector = ex[3].gridSelector; -ariaTest('Page up moves focus in example 3', exampleFile, 'key-page-up', async (t) => { - t.pass(3); + // Test focus moves focus and reveal hidden rows + await sendKeyToGridcell(t, gridSelector, 2, 1, Key.PAGE_DOWN); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 7, 1), + 'After sending PAGE DOWN to element at row 2 column 1, the focus should be on row 7, column 1' + ); - const gridSelector = ex[3].gridSelector; - await scrollToEndOfExample3(t); + await sendKeyToGridcell(t, gridSelector, 7, 3, Key.PAGE_DOWN); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 12, 3), + 'After sending PAGE DOWN to element at row 7 column 3, the focus should be on row 12, column 3' + ); - // Test focus moves left - await sendKeyToGridcell(t, gridSelector, 12, 1, Key.PAGE_UP); - t.true( - await checkFocusOnOrInCell(t, gridSelector, 11, 1,), - 'After sending PAGE UP to element at row 12 column 1, the focus should be on row 11, column 1' - ); + // Test focus does not wrap + await sendKeyToGridcell(t, gridSelector, 12, 4, Key.PAGE_DOWN); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 12, 4), + 'After sending PAGE DOWN to element at row 7 column 4, the focus should note move, because there are no more hidden rows' + ); + } +); - await sendKeyToGridcell(t, gridSelector, 11, 3, Key.PAGE_UP); - t.true( - await checkFocusOnOrInCell(t, gridSelector, 6, 3), - 'After sending PAGE UP to element at row 11 column 3, the focus should be on row 6, column 3' - ); +ariaTest( + 'Page up moves focus in example 3', + exampleFile, + 'key-page-up', + async (t) => { + t.pass(3); - // Test focus does not wrap - await sendKeyToGridcell(t, gridSelector, 6, 4, Key.PAGE_UP); - t.true( - await checkFocusOnOrInCell(t, gridSelector, 6, 4), - 'After sending PAGE UP to element at row 6 column 4, the focus should note move, because there are no more hidden rows' - ); + const gridSelector = ex[3].gridSelector; + await scrollToEndOfExample3(t); -}); + // Test focus moves left + await sendKeyToGridcell(t, gridSelector, 12, 1, Key.PAGE_UP); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 11, 1), + 'After sending PAGE UP to element at row 12 column 1, the focus should be on row 11, column 1' + ); + + await sendKeyToGridcell(t, gridSelector, 11, 3, Key.PAGE_UP); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 6, 3), + 'After sending PAGE UP to element at row 11 column 3, the focus should be on row 6, column 3' + ); + + // Test focus does not wrap + await sendKeyToGridcell(t, gridSelector, 6, 4, Key.PAGE_UP); + t.true( + await checkFocusOnOrInCell(t, gridSelector, 6, 4), + 'After sending PAGE UP to element at row 6 column 4, the focus should note move, because there are no more hidden rows' + ); + } +); ariaTest('Home key moves focus', exampleFile, 'key-home', async (t) => { - // Examples 1 and 2 and 3 - for (let example of [1,2,3]) { + for (let example of [1, 2, 3]) { const gridSelector = ex[example].gridSelector; const rowSelector = ex[example].rowSelector; const lastColumn = ex[example].lastColumn; @@ -589,10 +789,9 @@ ariaTest('Home key moves focus', exampleFile, 'key-home', async (t) => { // Index starts at 2 to skip header for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { - // If the row is not displayed, send page down to the last rows first element if (example === 3) { - let rows = (await t.context.queryElements(t, rowSelector)); + let rows = await t.context.queryElements(t, rowSelector); if (!(await rows[rowIndex - 1].isDisplayed())) { let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); await previousRowCell.sendKeys(Key.PAGE_DOWN); @@ -603,7 +802,14 @@ ariaTest('Home key moves focus', exampleFile, 'key-home', async (t) => { await sendKeyToGridcell(t, gridSelector, rowIndex, columnIndex, Key.HOME); t.true( await checkFocusOnOrInCell(t, gridSelector, rowIndex, 1), - 'After sending END to element in row ' + rowIndex + ' column ' + columnIndex + ', focus should be on element in row ' + rowIndex + ' column 1 for example: ' + example + 'After sending END to element in row ' + + rowIndex + + ' column ' + + columnIndex + + ', focus should be on element in row ' + + rowIndex + + ' column 1 for example: ' + + example ); // Switch the column every time @@ -611,15 +817,13 @@ ariaTest('Home key moves focus', exampleFile, 'key-home', async (t) => { if (columnIndex > lastColumn) { columnIndex = 1; } - } } }); ariaTest('End key moves focus', exampleFile, 'key-end', async (t) => { - // Examples 1 and 2 and 3 - for (let example of [1,2,3]) { + for (let example of [1, 2, 3]) { const gridSelector = ex[example].gridSelector; const rowSelector = ex[example].rowSelector; const lastColumn = ex[example].lastColumn; @@ -629,10 +833,9 @@ ariaTest('End key moves focus', exampleFile, 'key-end', async (t) => { // Index starts at 2 to skip header for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { - // If the row is not displayed, send page down to the last rows first element if (example === 3) { - let rows = (await t.context.queryElements(t, rowSelector)); + let rows = await t.context.queryElements(t, rowSelector); if (!(await rows[rowIndex - 1].isDisplayed())) { let previousRowCell = rows[rowIndex - 2].findElement(By.css('td')); await previousRowCell.sendKeys(Key.PAGE_DOWN); @@ -643,7 +846,16 @@ ariaTest('End key moves focus', exampleFile, 'key-end', async (t) => { await sendKeyToGridcell(t, gridSelector, rowIndex, columnIndex, Key.END); t.true( await checkFocusOnOrInCell(t, gridSelector, rowIndex, lastColumn), - 'After sending END to element in row ' + rowIndex + ' column ' + columnIndex + ', focus should be on element in row ' + rowIndex + ' column ' + lastColumn + ' for example: ' + example + 'After sending END to element in row ' + + rowIndex + + ' column ' + + columnIndex + + ', focus should be on element in row ' + + rowIndex + + ' column ' + + lastColumn + + ' for example: ' + + example ); // Switch the column every time @@ -651,91 +863,123 @@ ariaTest('End key moves focus', exampleFile, 'key-end', async (t) => { if (columnIndex > lastColumn) { columnIndex = 1; } - } } }); -ariaTest('Control+home moves focus', exampleFile, 'key-control-home', async (t) => { - - // Examples 1, 2, and 3 - for (let example of [1,2,3]) { - const gridSelector = ex[example].gridSelector; - const rowSelector = ex[example].rowSelector; - const lastColumn = ex[example].lastColumn; - const lastRow = ex[example].lastRow; - const firstInteractiveRow = ex[example].firstInteractiveRow; - - let columnIndex = 1; - - // Index starts at 2 to skip header - for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { - - // If the row is not displayed, send page down to display gridcells - if (example === 3) { - if (rowIndex >= 7) { - await sendKeyToGridcell(t, gridSelector, 2, 1, Key.PAGE_DOWN); - } - if (rowIndex >= 12) { - await sendKeyToGridcell(t, gridSelector, 7, 1, Key.PAGE_DOWN); +ariaTest( + 'Control+home moves focus', + exampleFile, + 'key-control-home', + async (t) => { + // Examples 1, 2, and 3 + for (let example of [1, 2, 3]) { + const gridSelector = ex[example].gridSelector; + const rowSelector = ex[example].rowSelector; + const lastColumn = ex[example].lastColumn; + const lastRow = ex[example].lastRow; + const firstInteractiveRow = ex[example].firstInteractiveRow; + + let columnIndex = 1; + + // Index starts at 2 to skip header + for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // If the row is not displayed, send page down to display gridcells + if (example === 3) { + if (rowIndex >= 7) { + await sendKeyToGridcell(t, gridSelector, 2, 1, Key.PAGE_DOWN); + } + if (rowIndex >= 12) { + await sendKeyToGridcell(t, gridSelector, 7, 1, Key.PAGE_DOWN); + } } - } - - // Test focus moves down - await sendKeyToGridcell(t, gridSelector, rowIndex, columnIndex, Key.chord(Key.CONTROL, Key.HOME)); - t.true( - await checkFocusOnOrInCell(t, gridSelector, firstInteractiveRow, 1), - 'After sending CONTROL+HOME to element in row ' + rowIndex + ' column ' + columnIndex + ', focus should be on element in row ' + firstInteractiveRow + ' column 1 for example: ' + example - ); - // Switch the column every time - columnIndex++; - if (columnIndex > lastColumn) { - columnIndex = 1; + // Test focus moves down + await sendKeyToGridcell( + t, + gridSelector, + rowIndex, + columnIndex, + Key.chord(Key.CONTROL, Key.HOME) + ); + t.true( + await checkFocusOnOrInCell(t, gridSelector, firstInteractiveRow, 1), + 'After sending CONTROL+HOME to element in row ' + + rowIndex + + ' column ' + + columnIndex + + ', focus should be on element in row ' + + firstInteractiveRow + + ' column 1 for example: ' + + example + ); + + // Switch the column every time + columnIndex++; + if (columnIndex > lastColumn) { + columnIndex = 1; + } } } } -}); - -ariaTest('Control+end moves focus', exampleFile, 'key-control-end', async (t) => { - - // Examples 1, 2, and 3 - for (let example of [1,2,3]) { - const gridSelector = ex[example].gridSelector; - const rowSelector = ex[example].rowSelector; - const lastColumn = ex[example].lastColumn; - const lastRow = ex[example].lastRow; - - let columnIndex = 1; - - // Index starts at 2 to skip header - for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { - - // If the row is not displayed, send page up to display gridcells - // This will happen after the first iteration of this loop because in the - // last round the focus end on the last row - if (example === 3 && rowIndex !== 2) { - if (rowIndex < 12) { - await sendKeyToGridcell(t, gridSelector, 12, 1, Key.PAGE_UP); +); + +ariaTest( + 'Control+end moves focus', + exampleFile, + 'key-control-end', + async (t) => { + // Examples 1, 2, and 3 + for (let example of [1, 2, 3]) { + const gridSelector = ex[example].gridSelector; + const rowSelector = ex[example].rowSelector; + const lastColumn = ex[example].lastColumn; + const lastRow = ex[example].lastRow; + + let columnIndex = 1; + + // Index starts at 2 to skip header + for (let rowIndex = 2; rowIndex <= lastRow; rowIndex++) { + // If the row is not displayed, send page up to display gridcells + // This will happen after the first iteration of this loop because in the + // last round the focus end on the last row + if (example === 3 && rowIndex !== 2) { + if (rowIndex < 12) { + await sendKeyToGridcell(t, gridSelector, 12, 1, Key.PAGE_UP); + } + if (rowIndex < 7) { + await sendKeyToGridcell(t, gridSelector, 7, 1, Key.PAGE_UP); + } } - if (rowIndex < 7) { - await sendKeyToGridcell(t, gridSelector, 7, 1, Key.PAGE_UP); - } - } - - // Test focus moves down - await sendKeyToGridcell(t, gridSelector, rowIndex, columnIndex, Key.chord(Key.CONTROL, Key.END)); - t.true( - await checkFocusOnOrInCell(t, gridSelector, lastRow, lastColumn), - 'After sending CONTROL+END to element in row ' + rowIndex + ' column ' + columnIndex + ', focus should be on element in row ' + lastRow + ' column ' + lastColumn + ' for example: ' + example - ); - // Switch the column every time - columnIndex++; - if (columnIndex > lastColumn) { - columnIndex = 1; + // Test focus moves down + await sendKeyToGridcell( + t, + gridSelector, + rowIndex, + columnIndex, + Key.chord(Key.CONTROL, Key.END) + ); + t.true( + await checkFocusOnOrInCell(t, gridSelector, lastRow, lastColumn), + 'After sending CONTROL+END to element in row ' + + rowIndex + + ' column ' + + columnIndex + + ', focus should be on element in row ' + + lastRow + + ' column ' + + lastColumn + + ' for example: ' + + example + ); + + // Switch the column every time + columnIndex++; + if (columnIndex > lastColumn) { + columnIndex = 1; + } } } } -}); - +); diff --git a/test/tests/link_link.js b/test/tests/link_link.js index 00a61b226e..b31b8a4dec 100644 --- a/test/tests/link_link.js +++ b/test/tests/link_link.js @@ -7,23 +7,25 @@ const assertAriaLabelExists = require('../util/assertAriaLabelExists'); let pageExamples = [ { exampleId: 'ex1', - linkSelector: '#ex1 span' + linkSelector: '#ex1 span', }, { exampleId: 'ex2', linkSelector: '#ex2 img', - alt: true + alt: true, }, { exampleId: 'ex3', linkSelector: '#ex3 span', - ariaLabel: true - } + ariaLabel: true, + }, ]; -ariaTest('Test "role" attribute exists', - 'link/link.html', 'link-role', async (t) => { - +ariaTest( + 'Test "role" attribute exists', + 'link/link.html', + 'link-role', + async (t) => { for (let i = 0; i < pageExamples.length; i++) { let ex = pageExamples[i]; let linkLocator = By.css(ex.linkSelector); @@ -32,14 +34,18 @@ ariaTest('Test "role" attribute exists', t.is( await linkElement.getAttribute('role'), 'link', - '[role="link"] attribute should exist on element select by: ' + ex.linkSelector + '[role="link"] attribute should exist on element select by: ' + + ex.linkSelector ); } - }); - -ariaTest('Test "tabindex" attribute set to 0', - 'link/link.html', 'tabindex', async (t) => { + } +); +ariaTest( + 'Test "tabindex" attribute set to 0', + 'link/link.html', + 'tabindex', + async (t) => { for (let i = 0; i < pageExamples.length; i++) { let ex = pageExamples[i]; let linkLocator = By.css(ex.linkSelector); @@ -48,36 +54,35 @@ ariaTest('Test "tabindex" attribute set to 0', t.is( await linkElement.getAttribute('tabindex'), '0', - '[tab-index=0] attribute should exist on element selected by: ' + ex.linkSelector + '[tab-index=0] attribute should exist on element selected by: ' + + ex.linkSelector ); } - }); - -ariaTest('Test "alt" attribute exists', - 'link/link.html', 'alt', async (t) => { - - for (let i = 0; i < pageExamples.length; i++) { - let ex = pageExamples[i]; - if (!Object.prototype.hasOwnProperty.call(ex, 'alt')) { - continue; - } - let linkLocator = By.css(ex.linkSelector); - let linkElement = await t.context.session.findElement(linkLocator); + } +); - t.truthy( - await linkElement.getAttribute('alt'), - '"alt" attribute should exist on element selected by: ' + ex.linkSelector - ); +ariaTest('Test "alt" attribute exists', 'link/link.html', 'alt', async (t) => { + for (let i = 0; i < pageExamples.length; i++) { + let ex = pageExamples[i]; + if (!Object.prototype.hasOwnProperty.call(ex, 'alt')) { + continue; } + let linkLocator = By.css(ex.linkSelector); + let linkElement = await t.context.session.findElement(linkLocator); - }); - -ariaTest('Test "aria-label" attribute exists', - 'link/link.html', 'aria-label', async (t) => { + t.truthy( + await linkElement.getAttribute('alt'), + '"alt" attribute should exist on element selected by: ' + ex.linkSelector + ); + } +}); - +ariaTest( + 'Test "aria-label" attribute exists', + 'link/link.html', + 'aria-label', + async (t) => { for (let i = 0; i < pageExamples.length; i++) { - let ex = pageExamples[i]; if (!Object.prototype.hasOwnProperty.call(ex, 'ariaLabel')) { continue; @@ -85,11 +90,14 @@ ariaTest('Test "aria-label" attribute exists', await assertAriaLabelExists(t, ex.linkSelector); } - }); - -ariaTest('Test "ENTER" key behavior', - 'link/link.html', 'key-enter', async (t) => { + } +); +ariaTest( + 'Test "ENTER" key behavior', + 'link/link.html', + 'key-enter', + async (t) => { for (let i = 0; i < pageExamples.length; i++) { await t.context.session.get(t.context.url); @@ -98,16 +106,21 @@ ariaTest('Test "ENTER" key behavior', let linkElement = await t.context.session.findElement(linkLocator); await linkElement.sendKeys(Key.ENTER); - await t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime).catch(() => {}); + await t.context.session + .wait(() => { + return t.context.session.getCurrentUrl().then((url) => { + return url != t.context.url; + }); + }, t.context.waitTime) + .catch(() => {}); t.not( await t.context.session.getCurrentUrl(), t.context.url, - 'ENTER key on element with selector "' + ex.linkSelector + '" should activate link.' + 'ENTER key on element with selector "' + + ex.linkSelector + + '" should activate link.' ); } - }); + } +); diff --git a/test/tests/listbox_collapsible.js b/test/tests/listbox_collapsible.js index 968f6f60ec..8f2aa10ea3 100644 --- a/test/tests/listbox_collapsible.js +++ b/test/tests/listbox_collapsible.js @@ -14,146 +14,223 @@ const ex = { buttonSelector: '#ex button', listboxSelector: '#ex [role="listbox"]', optionSelector: '#ex [role="option"]', - numOptions: 26 + numOptions: 26, }; const checkFocus = async function (t, selector) { - return await t.context.session.wait(async function () { - return t.context.session.executeScript(function () { - const [ selector ] = arguments; - const items = document.querySelector(selector); - return items === document.activeElement; - }, selector); - }, t.context.waitTime, 'Timeout waiting for dom focus on element:' + selector); + return await t.context.session.wait( + async function () { + return t.context.session.executeScript(function () { + const [selector] = arguments; + const items = document.querySelector(selector); + return items === document.activeElement; + }, selector); + }, + t.context.waitTime, + 'Timeout waiting for dom focus on element:' + selector + ); }; -ariaTest('"aria-labelledby" on button element', exampleFile, 'button-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on button element', + exampleFile, + 'button-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.buttonSelector); -}); - - -ariaTest('"aria-haspopup" on button element', exampleFile, 'button-aria-haspopup', async (t) => { - await assertAttributeValues(t, ex.buttonSelector, 'aria-haspopup', 'listbox'); -}); - -ariaTest('"aria-expanded" on button element', exampleFile, 'button-aria-expanded', async (t) => { - - const button = await t.context.session.findElement(By.css(ex.buttonSelector)); - - // Check that aria-expanded is not set and the listbox is not visible before interacting - - await assertAttributeDNE(t, ex.buttonSelector, 'aria-expanded'); - - t.false( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), - 'listbox element should not be displayed when \'aria-expanded\' does not exist' - ); - - // click button - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - - // Check that aria-expanded is true and the listbox is visible - - t.is( - await button.getAttribute('aria-expanded'), - 'true', - 'button element should have attribute "aria-expanded" set to true clicking.' - ); - - t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), - 'listbox element should be displayed when \'aria-expanded\' is true' - ); -}); + } +); + +ariaTest( + '"aria-haspopup" on button element', + exampleFile, + 'button-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.buttonSelector, + 'aria-haspopup', + 'listbox' + ); + } +); + +ariaTest( + '"aria-expanded" on button element', + exampleFile, + 'button-aria-expanded', + async (t) => { + const button = await t.context.session.findElement( + By.css(ex.buttonSelector) + ); + + // Check that aria-expanded is not set and the listbox is not visible before interacting + + await assertAttributeDNE(t, ex.buttonSelector, 'aria-expanded'); + + t.false( + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), + "listbox element should not be displayed when 'aria-expanded' does not exist" + ); + + // click button + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + + // Check that aria-expanded is true and the listbox is visible + + t.is( + await button.getAttribute('aria-expanded'), + 'true', + 'button element should have attribute "aria-expanded" set to true clicking.' + ); + + t.true( + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), + "listbox element should be displayed when 'aria-expanded' is true" + ); + } +); -ariaTest('role="listbox" on ul element', exampleFile, 'listbox-role', async (t) => { +ariaTest( + 'role="listbox" on ul element', + exampleFile, + 'listbox-role', + async (t) => { await assertAriaRoles(t, 'ex', 'listbox', 1, 'ul'); -}); + } +); -ariaTest('"aria-labelledby" on listbox element', exampleFile, 'listbox-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on listbox element', + exampleFile, + 'listbox-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.listboxSelector); -}); + } +); -ariaTest('tabindex="0" on listbox element', exampleFile, 'listbox-tabindex', async (t) => { +ariaTest( + 'tabindex="0" on listbox element', + exampleFile, + 'listbox-tabindex', + async (t) => { await assertAttributeValues(t, ex.listboxSelector, 'tabindex', '-1'); -}); - -ariaTest('aria-activedescendant on listbox element', exampleFile, 'listbox-aria-activedescendant', async (t) => { - // Put the focus on the listbox by clicking the button - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const options = await t.context.queryElements(t, ex.optionSelector); - const optionId = await options[0].getAttribute('id'); - - // no active descendant is expected until arrow keys are used - t.is(await listbox.getAttribute('aria-activedescendant'), null, 'activedescendant not set on open click'); - - // active descendant set to first item on down arrow - await listbox.sendKeys(Key.DOWN); - - t.is( - await listbox.getAttribute('aria-activedescendant'), - optionId, - 'aria-activedescendant should be set to ' + optionId + ' for items: ' + ex.listboxSelector - ); -}); + } +); + +ariaTest( + 'aria-activedescendant on listbox element', + exampleFile, + 'listbox-aria-activedescendant', + async (t) => { + // Put the focus on the listbox by clicking the button + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const options = await t.context.queryElements(t, ex.optionSelector); + const optionId = await options[0].getAttribute('id'); + + // no active descendant is expected until arrow keys are used + t.is( + await listbox.getAttribute('aria-activedescendant'), + null, + 'activedescendant not set on open click' + ); + + // active descendant set to first item on down arrow + await listbox.sendKeys(Key.DOWN); + + t.is( + await listbox.getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant should be set to ' + + optionId + + ' for items: ' + + ex.listboxSelector + ); + } +); -ariaTest('role="option" on li elements', exampleFile, 'option-role', async (t) => { +ariaTest( + 'role="option" on li elements', + exampleFile, + 'option-role', + async (t) => { await assertAriaRoles(t, 'ex', 'option', 26, 'li'); -}); - -ariaTest('"aria-selected" on option elements', exampleFile, 'option-aria-selected', async (t) => { - - await assertAttributeDNE(t, ex.optionSelector, 'aria-selected'); - - // Put the focus on the listbox with arrow down - await t.context.session.findElement(By.css(ex.buttonSelector)).sendKeys(Key.DOWN); - - await assertAttributeValues(t, ex.optionSelector + ':nth-child(1)', 'aria-selected', 'true'); -}); - + } +); + +ariaTest( + '"aria-selected" on option elements', + exampleFile, + 'option-aria-selected', + async (t) => { + await assertAttributeDNE(t, ex.optionSelector, 'aria-selected'); + + // Put the focus on the listbox with arrow down + await t.context.session + .findElement(By.css(ex.buttonSelector)) + .sendKeys(Key.DOWN); + + await assertAttributeValues( + t, + ex.optionSelector + ':nth-child(1)', + 'aria-selected', + 'true' + ); + } +); // Keys +ariaTest( + 'ENTER opens and closes listbox', + exampleFile, + 'key-enter', + async (t) => { + let button = await t.context.session.findElement(By.css(ex.buttonSelector)); + let listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); -ariaTest('ENTER opens and closes listbox', exampleFile, 'key-enter', async (t) => { - let button = await t.context.session.findElement(By.css(ex.buttonSelector)); - let listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - - await button.sendKeys(Key.ENTER); + await button.sendKeys(Key.ENTER); - t.true( - await listbox.isDisplayed(), - 'After sending ENTER to the button element, the listbox should open' - ); + t.true( + await listbox.isDisplayed(), + 'After sending ENTER to the button element, the listbox should open' + ); - // Send down arrow to change the selection - await listbox.sendKeys(Key.ARROW_DOWN); - await listbox.sendKeys(Key.ARROW_DOWN); + // Send down arrow to change the selection + await listbox.sendKeys(Key.ARROW_DOWN); + await listbox.sendKeys(Key.ARROW_DOWN); - let newSelectedText = await t.context.session.findElement( - By.css(ex.optionSelector + '[aria-selected="true"]') - ).getText(); + let newSelectedText = await t.context.session + .findElement(By.css(ex.optionSelector + '[aria-selected="true"]')) + .getText(); - // Send ENTER to the listbox - await listbox.sendKeys(Key.ENTER); + // Send ENTER to the listbox + await listbox.sendKeys(Key.ENTER); - // And focus should be on the element with the corresponding text in on button - t.false( - await listbox.isDisplayed(), - 'After sending ENTER to the listbox element, the listbox should closed' - ); + // And focus should be on the element with the corresponding text in on button + t.false( + await listbox.isDisplayed(), + 'After sending ENTER to the listbox element, the listbox should closed' + ); - t.is( - await button.getText(), - newSelectedText, - 'The button text should match the newly selected option in the listbox' - ); -}); + t.is( + await button.getText(), + newSelectedText, + 'The button text should match the newly selected option in the listbox' + ); + } +); ariaTest('ESCAPE closes listbox', exampleFile, 'key-escape', async (t) => { - let button = await t.context.session.findElement(By.css(ex.buttonSelector)); let listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); @@ -162,9 +239,9 @@ ariaTest('ESCAPE closes listbox', exampleFile, 'key-escape', async (t) => { // Send down arrow to change the selection await listbox.sendKeys(Key.ARROW_DOWN); - let newSelectedText = await t.context.session.findElement( - By.css(ex.optionSelector + '[aria-selected="true"]') - ).getText(); + let newSelectedText = await t.context.session + .findElement(By.css(ex.optionSelector + '[aria-selected="true"]')) + .getText(); // Send ESCAPE to the listbox await listbox.sendKeys(Key.ESCAPE); @@ -181,8 +258,8 @@ ariaTest('ESCAPE closes listbox', exampleFile, 'key-escape', async (t) => { 'The button text should match the newly selected option in the listbox' ); - let focusOnButton = await t.context.session.executeScript(function () { - const [ selector ] = arguments; + let focusOnButton = await t.context.session.executeScript(function () { + const [selector] = arguments; return document.querySelector(selector) === document.activeElement; }, ex.buttonSelector); @@ -190,41 +267,64 @@ ariaTest('ESCAPE closes listbox', exampleFile, 'key-escape', async (t) => { focusOnButton, 'After sending escape to the listbox, the focus should be on the button' ); - }); -ariaTest('DOWN ARROW opens listbox and moves focus', exampleFile, 'key-down-arrow', async (t) => { - - // Send DOWN ARROW to button should open listbox - await t.context.session.findElement(By.css(ex.buttonSelector)).sendKeys(Key.ARROW_DOWN); - - // Confirm the listbox is open and in focus - t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), - 'listbox element should be displayed after sending DOWN ARROW to button' - ); - t.true( - await checkFocus(t, ex.listboxSelector), - 'listbox element should have focus after sending DOWN ARROW to button' - ); - - // Confirm first option is selected - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); - - // Sending the key down arrow will put focus on the item at index 1 - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - await listbox.sendKeys(Key.ARROW_DOWN); - - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 1); - - // The selection does not wrap to beginning of list if keydown arrow is sent more times - // then their are options - for (let i = 0; i < ex.numOptions + 1; i++) { +ariaTest( + 'DOWN ARROW opens listbox and moves focus', + exampleFile, + 'key-down-arrow', + async (t) => { + // Send DOWN ARROW to button should open listbox + await t.context.session + .findElement(By.css(ex.buttonSelector)) + .sendKeys(Key.ARROW_DOWN); + + // Confirm the listbox is open and in focus + t.true( + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), + 'listbox element should be displayed after sending DOWN ARROW to button' + ); + t.true( + await checkFocus(t, ex.listboxSelector), + 'listbox element should have focus after sending DOWN ARROW to button' + ); + + // Confirm first option is selected + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); + + // Sending the key down arrow will put focus on the item at index 1 + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); await listbox.sendKeys(Key.ARROW_DOWN); + + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 1 + ); + + // The selection does not wrap to beginning of list if keydown arrow is sent more times + // then their are options + for (let i = 0; i < ex.numOptions + 1; i++) { + await listbox.sendKeys(Key.ARROW_DOWN); + } + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 1 + ); } - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 1); -}); +); ariaTest('END moves focus', exampleFile, 'key-end', async (t) => { t.pass(2); @@ -232,114 +332,177 @@ ariaTest('END moves focus', exampleFile, 'key-end', async (t) => { // Put the focus on the listbox by clicking the button await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); // Sending key end should put focus on the last item await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 1 + ); // Sending key end twice should put focus on the last item await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 1 + ); }); ariaTest('UP ARROW moves focus', exampleFile, 'key-up-arrow', async (t) => { - // Send UP ARROW to button should open listbox - await t.context.session.findElement(By.css(ex.buttonSelector)).sendKeys(Key.ARROW_UP); + await t.context.session + .findElement(By.css(ex.buttonSelector)) + .sendKeys(Key.ARROW_UP); // Confirm the listbox is open and in focus t.true( - await t.context.session.findElement(By.css(ex.listboxSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .isDisplayed(), 'listbox element should be displayed after sending UP ARROW to button' ); t.true( await checkFocus(t, ex.listboxSelector), 'listbox element should have focus after sending UP ARROW to button' ); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); // Sending key end should put focus on the last item await listbox.sendKeys(Key.END); // Sending the key up arrow will put focus on the item at index numOptions-2 await listbox.sendKeys(Key.ARROW_UP); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 2); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 2 + ); // The selection does not wrap to the bottom of list if key up arrow is sent more times // then their are options for (let i = 0; i < ex.numOptions + 1; i++) { await listbox.sendKeys(Key.ARROW_UP); } - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); }); ariaTest('HOME moves focus', exampleFile, 'key-home', async (t) => { - // Put the focus on the listbox by clicking the button await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); await listbox.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN); // Sending key home should always put focus on the first item await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); // Sending key home twice should always put focus on the first item await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); }); - -ariaTest('Character keys moves focus', exampleFile, 'key-character', async (t) => { - - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - - // The third item is 'Americium' - let listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - await listbox.sendKeys('a'); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 2); - - - // Reload page - await t.context.session.get(t.context.url); - - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - - // Keys in rapid session will treat characters like first few characters of item - // In this cae, 'Curium' at index 3 will be skipped for 'Californium' at index 5 - listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - await listbox.sendKeys('c', 'a'); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 5); - - - // Reload page - await t.context.session.get(t.context.url); - - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(ex.buttonSelector)).click(); - - listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - - // With a break, sending on 'b' will land us on 'Berkelium' at index 4 - await listbox.sendKeys('b'); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 4); - - // Wait for half a second before sending second 'b' - await new Promise((resolve) => setTimeout(resolve, 500)); - - // A second 'b' should land us on 'Bohrium' at index 14 - await listbox.sendKeys('b'); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 14); -}); +ariaTest( + 'Character keys moves focus', + exampleFile, + 'key-character', + async (t) => { + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + + // The third item is 'Americium' + let listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + await listbox.sendKeys('a'); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 2 + ); + + // Reload page + await t.context.session.get(t.context.url); + + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + + // Keys in rapid session will treat characters like first few characters of item + // In this cae, 'Curium' at index 3 will be skipped for 'Californium' at index 5 + listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + await listbox.sendKeys('c', 'a'); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 5 + ); + + // Reload page + await t.context.session.get(t.context.url); + + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. + await t.context.session.findElement(By.css(ex.buttonSelector)).click(); + + listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + + // With a break, sending on 'b' will land us on 'Berkelium' at index 4 + await listbox.sendKeys('b'); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 4 + ); + + // Wait for half a second before sending second 'b' + await new Promise((resolve) => setTimeout(resolve, 500)); + + // A second 'b' should land us on 'Bohrium' at index 14 + await listbox.sendKeys('b'); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 14 + ); + } +); diff --git a/test/tests/listbox_grouped.js b/test/tests/listbox_grouped.js index 7002104ff3..5787763ece 100644 --- a/test/tests/listbox_grouped.js +++ b/test/tests/listbox_grouped.js @@ -13,62 +13,110 @@ const exampleFile = 'listbox/listbox-grouped.html'; const ex = { listboxSelector: '#ex [role="listbox"]', optionSelector: '#ex [role="option"]', - optionIdBase: '#ss_elem_' + optionIdBase: '#ss_elem_', }; // Attributes -ariaTest('role="listbox" on ul element', exampleFile, 'listbox-role', async (t) => { - await assertAriaRoles(t, 'ex', 'listbox', 1, 'div'); -}); - -ariaTest('"aria-labelledby" on listbox element', exampleFile, 'listbox-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.listboxSelector); -}); - -ariaTest('tabindex="0" on listbox element', exampleFile, 'listbox-tabindex', async (t) => { - await assertAttributeValues(t, ex.listboxSelector, 'tabindex', '0'); -}); - -ariaTest('aria-activedescendant on listbox element', exampleFile, 'listbox-aria-activedescendant', async (t) => { - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(`${ex.optionIdBase}1`)).click(); - - let options = await t.context.queryElements(t, ex.optionSelector); - - let optionId = await options[0].getAttribute('id'); - - t.is( - await t.context.session - .findElement(By.css(ex.listboxSelector)) - .getAttribute('aria-activedescendant'), - optionId, - 'aria-activedescendant should be set to ' + optionId + ' for items: ' + ex.listboxSelector - ); -}); - -ariaTest('role="group" on ul elements', exampleFile, 'group-role', async (t) => { - await assertAriaRoles(t, 'ex', 'group', 3, 'ul'); -}); - -ariaTest('"aria-labelledby" on group elements', exampleFile, 'group-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, `${ex.listboxSelector} [role="group"]`); -}); - -ariaTest('role="option" on li elements', exampleFile, 'option-role', async (t) => { - await assertAriaRoles(t, 'ex', 'option', 11, 'li'); -}); - -ariaTest('"aria-selected" on option elements', exampleFile, 'option-aria-selected', async (t) => { - await assertAttributeDNE(t, ex.optionSelector, 'aria-selected'); - - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(`${ex.optionIdBase}1`)).click(); - - await assertAttributeValues(t, `${ex.optionIdBase}1`, 'aria-selected', 'true'); -}); +ariaTest( + 'role="listbox" on ul element', + exampleFile, + 'listbox-role', + async (t) => { + await assertAriaRoles(t, 'ex', 'listbox', 1, 'div'); + } +); + +ariaTest( + '"aria-labelledby" on listbox element', + exampleFile, + 'listbox-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.listboxSelector); + } +); + +ariaTest( + 'tabindex="0" on listbox element', + exampleFile, + 'listbox-tabindex', + async (t) => { + await assertAttributeValues(t, ex.listboxSelector, 'tabindex', '0'); + } +); + +ariaTest( + 'aria-activedescendant on listbox element', + exampleFile, + 'listbox-aria-activedescendant', + async (t) => { + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. + await t.context.session.findElement(By.css(`${ex.optionIdBase}1`)).click(); + + let options = await t.context.queryElements(t, ex.optionSelector); + + let optionId = await options[0].getAttribute('id'); + + t.is( + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant should be set to ' + + optionId + + ' for items: ' + + ex.listboxSelector + ); + } +); + +ariaTest( + 'role="group" on ul elements', + exampleFile, + 'group-role', + async (t) => { + await assertAriaRoles(t, 'ex', 'group', 3, 'ul'); + } +); + +ariaTest( + '"aria-labelledby" on group elements', + exampleFile, + 'group-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, `${ex.listboxSelector} [role="group"]`); + } +); + +ariaTest( + 'role="option" on li elements', + exampleFile, + 'option-role', + async (t) => { + await assertAriaRoles(t, 'ex', 'option', 11, 'li'); + } +); + +ariaTest( + '"aria-selected" on option elements', + exampleFile, + 'option-aria-selected', + async (t) => { + await assertAttributeDNE(t, ex.optionSelector, 'aria-selected'); + + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. + await t.context.session.findElement(By.css(`${ex.optionIdBase}1`)).click(); + + await assertAttributeValues( + t, + `${ex.optionIdBase}1`, + 'aria-selected', + 'true' + ); + } +); // Keys @@ -77,150 +125,294 @@ ariaTest('DOWN ARROW moves focus', exampleFile, 'key-down-arrow', async (t) => { await t.context.session.findElement(By.css(`${ex.optionIdBase}2`)).click(); // Sending the key down arrow will put focus on the item at index 2 (the third option) - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - await listbox.sendKeys(Key.ARROW_DOWN); - - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 2); -}); - -ariaTest('DOWN ARROW does not wrap to top of listbox', exampleFile, 'key-down-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const options = await t.context.queryElements(t, ex.optionSelector); - - // click the last option - await options[options.length - 1].click(); - - // arrow down - await listbox.sendKeys(Key.ARROW_DOWN); - - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, options.length - 1); -}); - -ariaTest('DOWN ARROW sends initial focus to the first option', exampleFile, 'key-down-arrow', async (t) => { - // Sending the key down arrow will put focus on the first option if no options are focused - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - await listbox.sendKeys(Key.ARROW_DOWN); - - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); -}); - -ariaTest('DOWN ARROW moves through groups', exampleFile, 'key-down-arrow', async (t) => { - // Sending the key down arrow will put focus on the first option if no options are focused - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const group1 = await listbox.findElement(By.css('[role="group"]')); - const groupOptions = await t.context.queryElements(t, '[role="option"]', group1); - - // click last option in group - await groupOptions[groupOptions.length - 1].click(); - + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); await listbox.sendKeys(Key.ARROW_DOWN); - // focus should be on the first option in the second group; i.e. index === groupOptions.length - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, groupOptions.length); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 2 + ); }); -ariaTest('UP ARROW sends initial focus to the first option', exampleFile, 'key-up-arrow', async (t) => { - // Sending the key up arrow will put focus on the first option, if no options are focused - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - await listbox.sendKeys(Key.ARROW_UP); - - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); -}); +ariaTest( + 'DOWN ARROW does not wrap to top of listbox', + exampleFile, + 'key-down-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const options = await t.context.queryElements(t, ex.optionSelector); + + // click the last option + await options[options.length - 1].click(); + + // arrow down + await listbox.sendKeys(Key.ARROW_DOWN); + + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + options.length - 1 + ); + } +); + +ariaTest( + 'DOWN ARROW sends initial focus to the first option', + exampleFile, + 'key-down-arrow', + async (t) => { + // Sending the key down arrow will put focus on the first option if no options are focused + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + await listbox.sendKeys(Key.ARROW_DOWN); + + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); + } +); + +ariaTest( + 'DOWN ARROW moves through groups', + exampleFile, + 'key-down-arrow', + async (t) => { + // Sending the key down arrow will put focus on the first option if no options are focused + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const group1 = await listbox.findElement(By.css('[role="group"]')); + const groupOptions = await t.context.queryElements( + t, + '[role="option"]', + group1 + ); + + // click last option in group + await groupOptions[groupOptions.length - 1].click(); + + await listbox.sendKeys(Key.ARROW_DOWN); + + // focus should be on the first option in the second group; i.e. index === groupOptions.length + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + groupOptions.length + ); + } +); + +ariaTest( + 'UP ARROW sends initial focus to the first option', + exampleFile, + 'key-up-arrow', + async (t) => { + // Sending the key up arrow will put focus on the first option, if no options are focused + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + await listbox.sendKeys(Key.ARROW_UP); + + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); + } +); ariaTest('END moves focus', exampleFile, 'key-end', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); const options = await t.context.queryElements(t, ex.optionSelector); // Sending key end should put focus on the last item await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - options.length - 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + options.length - 1 + ); // Sending key end twice should put focus on the last item await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - options.length - 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + options.length - 1 + ); }); ariaTest('UP ARROW moves focus', exampleFile, 'key-up-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); // Click the second option await t.context.session.findElement(By.css(`${ex.optionIdBase}2`)).click(); // Sending the key up arrow will put focus on the first option await listbox.sendKeys(Key.ARROW_UP); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); -}); - -ariaTest('UP ARROW does not wrap to bottom of listbox', exampleFile, 'key-up-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - - // click the first option - await t.context.session.findElement(By.css(`${ex.optionIdBase}1`)).click(); - - // arrow up - await listbox.sendKeys(Key.ARROW_UP); - - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); }); -ariaTest('UP ARROW moves through groups', exampleFile, 'key-up-arrow', async (t) => { - // Sending the key down arrow will put focus on the first option if no options are focused - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const group1 = await listbox.findElement(By.css('[role="group"]')); - const groupOptions = await t.context.queryElements(t, '[role="option"]', group1); - - // click first option in second group - await t.context.session.findElement(By.css(`${ex.optionIdBase}${groupOptions.length + 1}`)).click(); +ariaTest( + 'UP ARROW does not wrap to bottom of listbox', + exampleFile, + 'key-up-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + + // click the first option + await t.context.session.findElement(By.css(`${ex.optionIdBase}1`)).click(); + + // arrow up + await listbox.sendKeys(Key.ARROW_UP); + + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); + } +); + +ariaTest( + 'UP ARROW moves through groups', + exampleFile, + 'key-up-arrow', + async (t) => { + // Sending the key down arrow will put focus on the first option if no options are focused + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const group1 = await listbox.findElement(By.css('[role="group"]')); + const groupOptions = await t.context.queryElements( + t, + '[role="option"]', + group1 + ); + + // click first option in second group + await t.context.session + .findElement(By.css(`${ex.optionIdBase}${groupOptions.length + 1}`)) + .click(); - await listbox.sendKeys(Key.ARROW_UP); + await listbox.sendKeys(Key.ARROW_UP); - // focus should be on the last option in the first group - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, groupOptions.length - 1); -}); + // focus should be on the last option in the first group + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + groupOptions.length - 1 + ); + } +); ariaTest('HOME moves focus', exampleFile, 'key-home', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); await listbox.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN); // Sending key home should always put focus on the first item await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); // Sending key home twice should always put focus on the first item await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); -}); - -ariaTest('END scrolls listbox option into view', exampleFile, 'key-end', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const options = await t.context.queryElements(t, ex.optionSelector); - - let listboxBounds = await listbox.getRect(); - let optionBounds = await options[options.length - 1].getRect(); - - t.true(listboxBounds.y + listboxBounds.height - optionBounds.y < 0, 'last option is not initially displayed'); - - await listbox.sendKeys(Key.END); - listboxBounds = await listbox.getRect(); - optionBounds = await options[options.length - 1].getRect(); - - t.true(listboxBounds.y + listboxBounds.height - optionBounds.y >= 0, 'last option is in view after end key'); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); }); -ariaTest('Click scrolls listbox option into view', exampleFile, 'key-end', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); - const options = await t.context.queryElements(t, ex.optionSelector); - - let listboxBounds = await listbox.getRect(); - let optionBounds = await options[options.length - 1].getRect(); - - t.true(listboxBounds.y + listboxBounds.height - optionBounds.y < 0, 'last option is not initially displayed'); - - await options[options.length - 1].click(); - listboxBounds = await listbox.getRect(); - optionBounds = await options[options.length - 1].getRect(); - - t.true(listboxBounds.y + listboxBounds.height - optionBounds.y >= 0, 'last option is in view after click'); -}); +ariaTest( + 'END scrolls listbox option into view', + exampleFile, + 'key-end', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const options = await t.context.queryElements(t, ex.optionSelector); + + let listboxBounds = await listbox.getRect(); + let optionBounds = await options[options.length - 1].getRect(); + + t.true( + listboxBounds.y + listboxBounds.height - optionBounds.y < 0, + 'last option is not initially displayed' + ); + + await listbox.sendKeys(Key.END); + listboxBounds = await listbox.getRect(); + optionBounds = await options[options.length - 1].getRect(); + + t.true( + listboxBounds.y + listboxBounds.height - optionBounds.y >= 0, + 'last option is in view after end key' + ); + } +); + +ariaTest( + 'Click scrolls listbox option into view', + exampleFile, + 'key-end', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); + const options = await t.context.queryElements(t, ex.optionSelector); + + let listboxBounds = await listbox.getRect(); + let optionBounds = await options[options.length - 1].getRect(); + + t.true( + listboxBounds.y + listboxBounds.height - optionBounds.y < 0, + 'last option is not initially displayed' + ); + + await options[options.length - 1].click(); + listboxBounds = await listbox.getRect(); + optionBounds = await options[options.length - 1].getRect(); + + t.true( + listboxBounds.y + listboxBounds.height - optionBounds.y >= 0, + 'last option is in view after click' + ); + } +); diff --git a/test/tests/listbox_rearrangeable.js b/test/tests/listbox_rearrangeable.js index 307d6fee28..f48145ed09 100644 --- a/test/tests/listbox_rearrangeable.js +++ b/test/tests/listbox_rearrangeable.js @@ -18,7 +18,7 @@ const ex = { optionSelector: '#ex1 [role="option"]', numOptions: 10, firstOptionSelector: '#ex1 #ss_opt1', - lastOptionSelector: '#ex1 #ss_opt10' + lastOptionSelector: '#ex1 #ss_opt10', }, 2: { listboxSelector: '#ex2 [role="listbox"]', @@ -26,186 +26,310 @@ const ex = { optionSelector: '#ex2 [role="option"]', numOptions: 10, firstOptionSelector: '#ex2 #ms_opt1', - lastOptionSelector: '#ex2 #ms_opt10' - } + lastOptionSelector: '#ex2 #ms_opt10', + }, }; // Attributes -ariaTest('role="listbox" on ul element', exampleFile, 'listbox-role', async (t) => { +ariaTest( + 'role="listbox" on ul element', + exampleFile, + 'listbox-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'listbox', 2, 'ul'); - await assertAriaRoles(t, 'ex2', 'listbox', 2, 'ul'); -}); + await assertAriaRoles(t, 'ex2', 'listbox', 2, 'ul'); + } +); -ariaTest('"aria-labelledby" on listbox element', exampleFile, 'listbox-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on listbox element', + exampleFile, + 'listbox-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex[1].listboxSelector); - await assertAriaLabelledby(t, ex[2].listboxSelector); -}); + await assertAriaLabelledby(t, ex[2].listboxSelector); + } +); -ariaTest('tabindex="0" on listbox element', exampleFile, 'listbox-tabindex', async (t) => { +ariaTest( + 'tabindex="0" on listbox element', + exampleFile, + 'listbox-tabindex', + async (t) => { await assertAttributeValues(t, ex[1].listboxSelector, 'tabindex', '0'); - await assertAttributeValues(t, ex[2].listboxSelector, 'tabindex', '0'); -}); - -ariaTest('aria-multiselectable on listbox element', exampleFile, 'listbox-aria-multiselectable', async (t) => { - await assertAttributeValues(t, ex[2].listboxSelector, 'aria-multiselectable', 'true'); -}); - - -ariaTest('aria-activedescendant on listbox element', exampleFile, 'listbox-aria-activedescendant', async (t) => { - - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(ex[1].firstOptionSelector)).click(); - - let options = await t.context.queryElements(t, ex[1].optionSelector); - let optionId = await options[0].getAttribute('id'); - - t.is( + await assertAttributeValues(t, ex[2].listboxSelector, 'tabindex', '0'); + } +); + +ariaTest( + 'aria-multiselectable on listbox element', + exampleFile, + 'listbox-aria-multiselectable', + async (t) => { + await assertAttributeValues( + t, + ex[2].listboxSelector, + 'aria-multiselectable', + 'true' + ); + } +); + +ariaTest( + 'aria-activedescendant on listbox element', + exampleFile, + 'listbox-aria-activedescendant', + async (t) => { + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. await t.context.session - .findElement(By.css(ex[1].listboxSelector)) - .getAttribute('aria-activedescendant'), - optionId, - 'aria-activedescendant should be set to ' + optionId + ' for items: ' + ex.listboxSelector - ); + .findElement(By.css(ex[1].firstOptionSelector)) + .click(); - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); + let options = await t.context.queryElements(t, ex[1].optionSelector); + let optionId = await options[0].getAttribute('id'); - options = await t.context.queryElements(t, ex[2].optionSelector); - optionId = await options[0].getAttribute('id'); + t.is( + await t.context.session + .findElement(By.css(ex[1].listboxSelector)) + .getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant should be set to ' + + optionId + + ' for items: ' + + ex.listboxSelector + ); - t.is( + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. await t.context.session - .findElement(By.css(ex[2].listboxSelector)) - .getAttribute('aria-activedescendant'), - optionId, - 'aria-activedescendant should be set to ' + optionId + ' for items: ' + ex.listboxSelector - ); + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); -}); + options = await t.context.queryElements(t, ex[2].optionSelector); + optionId = await options[0].getAttribute('id'); -ariaTest('role="option" on li elements', exampleFile, 'option-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'option', 10, 'li'); - await assertAriaRoles(t, 'ex2', 'option', 10, 'li'); -}); + t.is( + await t.context.session + .findElement(By.css(ex[2].listboxSelector)) + .getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant should be set to ' + + optionId + + ' for items: ' + + ex.listboxSelector + ); + } +); -ariaTest('"aria-selected" on option elements', exampleFile, 'option-aria-selected', async (t) => { - - await assertAttributeDNE(t, ex[1].optionSelector, 'aria-selected'); - await t.context.session.findElement(By.css(ex[1].firstOptionSelector)).click(); - await assertAttributeValues(t, ex[1].optionSelector + ':nth-child(1)', 'aria-selected', 'true'); +ariaTest( + 'role="option" on li elements', + exampleFile, + 'option-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'option', 10, 'li'); + await assertAriaRoles(t, 'ex2', 'option', 10, 'li'); + } +); + +ariaTest( + '"aria-selected" on option elements', + exampleFile, + 'option-aria-selected', + async (t) => { + await assertAttributeDNE(t, ex[1].optionSelector, 'aria-selected'); + await t.context.session + .findElement(By.css(ex[1].firstOptionSelector)) + .click(); + await assertAttributeValues( + t, + ex[1].optionSelector + ':nth-child(1)', + 'aria-selected', + 'true' + ); - await assertAttributeValues(t, ex[2].optionSelector, 'aria-selected', 'false'); - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - await assertAttributeValues(t, ex[2].optionSelector + ':nth-child(1)', 'aria-selected', 'true'); -}); + await assertAttributeValues( + t, + ex[2].optionSelector, + 'aria-selected', + 'false' + ); + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); + await assertAttributeValues( + t, + ex[2].optionSelector + ':nth-child(1)', + 'aria-selected', + 'true' + ); + } +); // Keys -ariaTest('down arrow moves focus and selects', exampleFile, 'key-down-arrow', async (t) => { - - // Example 1 +ariaTest( + 'down arrow moves focus and selects', + exampleFile, + 'key-down-arrow', + async (t) => { + // Example 1 - let listbox = (await t.context.queryElements(t, ex[1].listboxSelector))[0]; - let options = await t.context.queryElements(t, ex[1].optionSelector); + let listbox = (await t.context.queryElements(t, ex[1].listboxSelector))[0]; + let options = await t.context.queryElements(t, ex[1].optionSelector); - // Put the focus on the first item - await t.context.session.findElement(By.css(ex[1].firstOptionSelector)).click(); - for (let index = 0; index < options.length - 1; index++) { + // Put the focus on the first item + await t.context.session + .findElement(By.css(ex[1].firstOptionSelector)) + .click(); + for (let index = 0; index < options.length - 1; index++) { + await listbox.sendKeys(Key.ARROW_DOWN); + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + index + 1 + ); + } + + // Send down arrow to the last option, focus should not move await listbox.sendKeys(Key.ARROW_DOWN); - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, index + 1); - } + let lastOption = options.length - 1; + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + lastOption + ); - // Send down arrow to the last option, focus should not move - await listbox.sendKeys(Key.ARROW_DOWN); - let lastOption = options.length - 1; - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, lastOption); + // Example 2 - // Example 2 + listbox = (await t.context.queryElements(t, ex[2].listboxSelector))[0]; + options = await t.context.queryElements(t, ex[2].optionSelector); - listbox = (await t.context.queryElements(t, ex[2].listboxSelector))[0]; - options = await t.context.queryElements(t, ex[2].optionSelector); + // Put the focus on the first item, and selects item, so skip by sending down arrow once + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); + await listbox.sendKeys(Key.ARROW_DOWN); - // Put the focus on the first item, and selects item, so skip by sending down arrow once - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - await listbox.sendKeys(Key.ARROW_DOWN); + for (let index = 1; index < options.length - 1; index++) { + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + index + ); + t.is( + await (await t.context.queryElements(t, ex[2].optionSelector))[ + index + ].getAttribute('aria-selected'), + 'false', + 'aria-selected is false when moving between options with down arrow in example 2' + ); + await listbox.sendKeys(Key.ARROW_DOWN); + } - for (let index = 1; index < options.length - 1; index++) { - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, index); + // Send down arrow to the last option, focus should not move + await listbox.sendKeys(Key.ARROW_DOWN); + lastOption = options.length - 1; + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + lastOption + ); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[index] - .getAttribute('aria-selected'), + await (await t.context.queryElements(t, ex[2].optionSelector))[ + lastOption + ].getAttribute('aria-selected'), 'false', 'aria-selected is false when moving between options with down arrow in example 2' ); - await listbox.sendKeys(Key.ARROW_DOWN); } +); - // Send down arrow to the last option, focus should not move - await listbox.sendKeys(Key.ARROW_DOWN); - lastOption = options.length - 1; - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, lastOption); - t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[lastOption] - .getAttribute('aria-selected'), - 'false', - 'aria-selected is false when moving between options with down arrow in example 2' - ); -}); +ariaTest( + 'up arrow moves focus and selects', + exampleFile, + 'key-up-arrow', + async (t) => { + // Example 1 -ariaTest('up arrow moves focus and selects', exampleFile, 'key-up-arrow', async (t) => { - - // Example 1 + let listbox = (await t.context.queryElements(t, ex[1].listboxSelector))[0]; + let options = await t.context.queryElements(t, ex[1].optionSelector); - let listbox = (await t.context.queryElements(t, ex[1].listboxSelector))[0]; - let options = await t.context.queryElements(t, ex[1].optionSelector); + // Put the focus on the first item + await t.context.session + .findElement(By.css(ex[1].lastOptionSelector)) + .click(); + for (let index = options.length - 1; index > 0; index--) { + await listbox.sendKeys(Key.ARROW_UP); + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + index - 1 + ); + } - // Put the focus on the first item - await t.context.session.findElement(By.css(ex[1].lastOptionSelector)).click(); - for (let index = options.length - 1; index > 0; index--) { + // Sending up arrow to first option, focus should not move await listbox.sendKeys(Key.ARROW_UP); - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, index - 1); - } + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + 0 + ); - // Sending up arrow to first option, focus should not move - await listbox.sendKeys(Key.ARROW_UP); - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, 0); + // Example 2 - // Example 2 + listbox = (await t.context.queryElements(t, ex[2].listboxSelector))[0]; + options = await t.context.queryElements(t, ex[2].optionSelector); - listbox = (await t.context.queryElements(t, ex[2].listboxSelector))[0]; - options = await t.context.queryElements(t, ex[2].optionSelector); + // Put the focus on the last item, and selects item, so skip by sending down arrow once + await t.context.session + .findElement(By.css(ex[2].lastOptionSelector)) + .click(); + await listbox.sendKeys(Key.ARROW_UP); - // Put the focus on the last item, and selects item, so skip by sending down arrow once - await t.context.session.findElement(By.css(ex[2].lastOptionSelector)).click(); - await listbox.sendKeys(Key.ARROW_UP); + for (let index = options.length - 2; index > 0; index--) { + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + index + ); + t.is( + await (await t.context.queryElements(t, ex[2].optionSelector))[ + index + ].getAttribute('aria-selected'), + 'false', + 'aria-selected is false when moving between options with down arrow in example 2' + ); + await listbox.sendKeys(Key.ARROW_UP); + } - for (let index = options.length - 2; index > 0; index--) { - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, index); + // Send down arrow to the last option, focus should not move + await listbox.sendKeys(Key.ARROW_UP); + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + 0 + ); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[index] - .getAttribute('aria-selected'), + await ( + await t.context.queryElements(t, ex[2].optionSelector) + )[0].getAttribute('aria-selected'), 'false', 'aria-selected is false when moving between options with down arrow in example 2' ); - await listbox.sendKeys(Key.ARROW_UP); } - - // Send down arrow to the last option, focus should not move - await listbox.sendKeys(Key.ARROW_UP); - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, 0); - t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[0] - .getAttribute('aria-selected'), - 'false', - 'aria-selected is false when moving between options with down arrow in example 2' - ); -}); +); ariaTest('home moves focus and selects', exampleFile, 'key-home', async (t) => { - // Example 1 let listbox = (await t.context.queryElements(t, ex[1].listboxSelector))[0]; @@ -214,13 +338,22 @@ ariaTest('home moves focus and selects', exampleFile, 'key-home', async (t) => { // Put the focus on the second item await options[1].click(); await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + 0 + ); // Put the focus on the last item await options[options.length - 1].click(); await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, 0); - + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + 0 + ); // Example 2 @@ -230,10 +363,16 @@ ariaTest('home moves focus and selects', exampleFile, 'key-home', async (t) => { // Put the focus on the second item await options[1].click(); await listbox.sendKeys(Key.HOME); - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, 0); + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + 0 + ); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[0] - .getAttribute('aria-selected'), + await ( + await t.context.queryElements(t, ex[2].optionSelector) + )[0].getAttribute('aria-selected'), 'false', 'aria-selected is false when moving between options with HOME in example 2' ); @@ -241,17 +380,22 @@ ariaTest('home moves focus and selects', exampleFile, 'key-home', async (t) => { // Put the focus on the last item await options[options.length - 1].click(); await listbox.sendKeys(Key.HOME); - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, 0); + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + 0 + ); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[0] - .getAttribute('aria-selected'), + await ( + await t.context.queryElements(t, ex[2].optionSelector) + )[0].getAttribute('aria-selected'), 'false', 'aria-selected is false when moving between options with HOME in example 2' ); }); ariaTest('end moves focus and selects', exampleFile, 'key-end', async (t) => { - // Example 1 let listbox = (await t.context.queryElements(t, ex[1].listboxSelector))[0]; @@ -261,13 +405,22 @@ ariaTest('end moves focus and selects', exampleFile, 'key-end', async (t) => { // Put the focus on the second item await options[1].click(); await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, lastOption); + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + lastOption + ); // Put the focus on the last item await options[lastOption - 1].click(); await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex[1].importantSelector, ex[1].optionSelector, lastOption); - + await assertAriaSelectedAndActivedescendant( + t, + ex[1].importantSelector, + ex[1].optionSelector, + lastOption + ); // Example 2 @@ -277,10 +430,16 @@ ariaTest('end moves focus and selects', exampleFile, 'key-end', async (t) => { // Put the focus on the second item await options[1].click(); await listbox.sendKeys(Key.END); - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, lastOption); + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + lastOption + ); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[lastOption] - .getAttribute('aria-selected'), + await (await t.context.queryElements(t, ex[2].optionSelector))[ + lastOption + ].getAttribute('aria-selected'), 'false', 'aria-selected is false when moving between options with END in example 2' ); @@ -288,350 +447,562 @@ ariaTest('end moves focus and selects', exampleFile, 'key-end', async (t) => { // Put the focus on the last item await options[lastOption - 1].click(); await listbox.sendKeys(Key.END); - await assertAriaActivedescendant(t, ex[2].availableSelector, ex[2].optionSelector, lastOption); + await assertAriaActivedescendant( + t, + ex[2].availableSelector, + ex[2].optionSelector, + lastOption + ); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[lastOption] - .getAttribute('aria-selected'), + await (await t.context.queryElements(t, ex[2].optionSelector))[ + lastOption + ].getAttribute('aria-selected'), 'false', 'aria-selected is false when moving between options with END in example 2' ); - }); ariaTest('key space selects', exampleFile, 'key-space', async (t) => { - const listbox = (await t.context.queryElements(t, ex[2].listboxSelector))[0]; const options = await t.context.queryElements(t, ex[2].optionSelector); // Put the focus on the first item, and selects item - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); for (let index = 0; index < options.length - 1; index++) { await listbox.sendKeys(Key.ARROW_DOWN, Key.SPACE); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[index + 1] - .getAttribute('aria-selected'), + await (await t.context.queryElements(t, ex[2].optionSelector))[ + index + 1 + ].getAttribute('aria-selected'), 'true', - 'aria-selected is true when sending space key to item at index: ' + (index + 1) + 'aria-selected is true when sending space key to item at index: ' + + (index + 1) ); } - for (let index = options.length - 1; index >= 0 ; index--) { + for (let index = options.length - 1; index >= 0; index--) { await listbox.sendKeys(Key.SPACE); t.is( - await(await t.context.queryElements(t, ex[2].optionSelector))[index] - .getAttribute('aria-selected'), + await (await t.context.queryElements(t, ex[2].optionSelector))[ + index + ].getAttribute('aria-selected'), 'false', - 'aria-selected is true when sending space key to item at index: ' + (index) + 'aria-selected is true when sending space key to item at index: ' + index ); await listbox.sendKeys(Key.ARROW_UP); } }); -ariaTest('shift + click selects multiple options', exampleFile, 'key-shift-up-arrow', async (t) => { - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the fourth item, and selects item - await options[3].click(); - - // send shift + click to first option - const actions = t.context.session.actions(); - await actions - .keyDown(Key.SHIFT) - .click(options[0]) - .keyUp(Key.SHIFT) - .perform(); - - // expect first through fourth option to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index < 4; - t.is( - selected, - `${shouldBeSelected}`, - `aria-selected should be ${shouldBeSelected} for option ${index + 1} after using shift + click` - ); +ariaTest( + 'shift + click selects multiple options', + exampleFile, + 'key-shift-up-arrow', + async (t) => { + const options = await t.context.queryElements(t, ex[2].optionSelector); + + // Put the focus on the fourth item, and selects item + await options[3].click(); + + // send shift + click to first option + const actions = t.context.session.actions(); + await actions + .keyDown(Key.SHIFT) + .click(options[0]) + .keyUp(Key.SHIFT) + .perform(); + + // expect first through fourth option to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index < 4; + t.is( + selected, + `${shouldBeSelected}`, + `aria-selected should be ${shouldBeSelected} for option ${ + index + 1 + } after using shift + click` + ); + } } -}); - -ariaTest('key shift+down arrow moves focus and selects', exampleFile, 'key-shift-down-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the first item, and selects item - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); - - // expect first two items to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index < 2; - t.is( - selected, - `${shouldBeSelected}`, - `aria-selected should be ${shouldBeSelected} for option ${index + 1} after using shift + down arrow to select first and second options` +); + +ariaTest( + 'key shift+down arrow moves focus and selects', + exampleFile, + 'key-shift-down-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) ); - } -}); - -ariaTest('key shift+down arrow overwrites previous selection', exampleFile, 'key-shift-down-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Select first item - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - - // set focus to 3rd item and select - await t.context.session.findElement(By.css(`${ex[2].optionSelector}:nth-child(3)`)).click(); - listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // expect only third and fourth items to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index === 2 || index === 3; - t.is( - selected, - `${shouldBeSelected}`, - `aria-selected should be ${shouldBeSelected} for option ${index + 1} after using shift + down arrow to select first and second options` - ); + // Put the focus on the first item, and selects item + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); + listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); + + // expect first two items to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index < 2; + t.is( + selected, + `${shouldBeSelected}`, + `aria-selected should be ${shouldBeSelected} for option ${ + index + 1 + } after using shift + down arrow to select first and second options` + ); + } } -}); - -ariaTest('key shift+down arrow cannot move past last option', exampleFile, 'key-shift-down-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the second to last item, and select item - await t.context.session.findElement(By.css(`${ex[2].optionSelector}:nth-child(${options.length - 1})`)).click(); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); - - // expect first two items to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index >= options.length - 2; - t.is( - selected, - `${shouldBeSelected}`, - `aria-selected should be ${shouldBeSelected} for option ${index + 1} after using shift + down arrow to select past last option` +); + +ariaTest( + 'key shift+down arrow overwrites previous selection', + exampleFile, + 'key-shift-down-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) ); - } -}); - -ariaTest('key shift+up arrow moves focus and selects', exampleFile, 'key-shift-up-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // Put the focus on the last item, and selects item - await t.context.session.findElement(By.css(ex[2].lastOptionSelector)).click(); - listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); + // Select first item + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); - // expect last two items to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index >= options.length - 2; - t.is( - selected, - `${shouldBeSelected}`, - `aria-selected should be ${shouldBeSelected} for option ${index + 1} after using shift + up arrow to select last and second-to-last options` - ); + // set focus to 3rd item and select + await t.context.session + .findElement(By.css(`${ex[2].optionSelector}:nth-child(3)`)) + .click(); + listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); + + // expect only third and fourth items to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index === 2 || index === 3; + t.is( + selected, + `${shouldBeSelected}`, + `aria-selected should be ${shouldBeSelected} for option ${ + index + 1 + } after using shift + down arrow to select first and second options` + ); + } } -}); - -ariaTest('key shift+up arrow resets previous selection', exampleFile, 'key-shift-up-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Select first item - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - - // set focus to 3rd item and select - await t.context.session.findElement(By.css(`${ex[2].optionSelector}:nth-child(3)`)).click(); - listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); - - // expect only second and third items to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index === 1 || index === 2; - t.is( - selected, - `${shouldBeSelected}`, - `aria-selected should be ${shouldBeSelected} for option ${index + 1} after using shift + up arrow to select third and second options` +); + +ariaTest( + 'key shift+down arrow cannot move past last option', + exampleFile, + 'key-shift-down-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) ); - } -}); + const options = await t.context.queryElements(t, ex[2].optionSelector); -ariaTest('key shift+up arrow cannot move past first option', exampleFile, 'key-shift-up-arrow', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the second item, and select item - await t.context.session.findElement(By.css(`${ex[2].optionSelector}:nth-child(2)`)).click(); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); - - // expect first two items to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index <= 1; - t.is( - selected, - `${shouldBeSelected}`, - `aria-selected should be ${shouldBeSelected} for option ${index + 1} after using shift + up arrow to select first and second` - ); + // Put the focus on the second to last item, and select item + await t.context.session + .findElement( + By.css(`${ex[2].optionSelector}:nth-child(${options.length - 1})`) + ) + .click(); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_DOWN)); + + // expect first two items to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index >= options.length - 2; + t.is( + selected, + `${shouldBeSelected}`, + `aria-selected should be ${shouldBeSelected} for option ${ + index + 1 + } after using shift + down arrow to select past last option` + ); + } } -}); - -ariaTest('key control+shift+home moves focus and selects all options', exampleFile, 'key-control-shift-home', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the last item, and selects item - await t.context.session.findElement(By.css(ex[2].lastOptionSelector)).click(); - listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.HOME)); +); + +ariaTest( + 'key shift+up arrow moves focus and selects', + exampleFile, + 'key-shift-up-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - t.is(selected, 'true', 'aria-selected should be true for all options after using shift + control + home from last option'); + // Put the focus on the last item, and selects item + await t.context.session + .findElement(By.css(ex[2].lastOptionSelector)) + .click(); + listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); + + // expect last two items to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index >= options.length - 2; + t.is( + selected, + `${shouldBeSelected}`, + `aria-selected should be ${shouldBeSelected} for option ${ + index + 1 + } after using shift + up arrow to select last and second-to-last options` + ); + } } -}); - -ariaTest('key control+shift+home moves focus and selects some options', exampleFile, 'key-control-shift-home', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); +); + +ariaTest( + 'key shift+up arrow resets previous selection', + exampleFile, + 'key-shift-up-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // Put the focus on the 5th option, arrow up, then do home - await t.context.session.findElement(By.css(`${ex[2].optionSelector}:nth-child(5)`)).click(); - await listbox.sendKeys(Key.ARROW_UP); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.HOME)); + // Select first item + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); - // expect 1st-4th options to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index < 4; - t.is(selected, `${shouldBeSelected}`, 'aria-selected should be true for first through fourth options'); + // set focus to 3rd item and select + await t.context.session + .findElement(By.css(`${ex[2].optionSelector}:nth-child(3)`)) + .click(); + listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); + + // expect only second and third items to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index === 1 || index === 2; + t.is( + selected, + `${shouldBeSelected}`, + `aria-selected should be ${shouldBeSelected} for option ${ + index + 1 + } after using shift + up arrow to select third and second options` + ); + } } -}); - -ariaTest('key shift+home does not change selection', exampleFile, 'key-control-shift-home', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the last option and select it - await t.context.session.findElement(By.css(ex[2].lastOptionSelector)).click(); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.HOME)); +); + +ariaTest( + 'key shift+up arrow cannot move past first option', + exampleFile, + 'key-shift-up-arrow', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // expect only last item - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index === options.length - 1; - t.is(selected, `${shouldBeSelected}`, 'aria-selected should only be true for last option'); + // Put the focus on the second item, and select item + await t.context.session + .findElement(By.css(`${ex[2].optionSelector}:nth-child(2)`)) + .click(); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.ARROW_UP)); + + // expect first two items to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index <= 1; + t.is( + selected, + `${shouldBeSelected}`, + `aria-selected should be ${shouldBeSelected} for option ${ + index + 1 + } after using shift + up arrow to select first and second` + ); + } } -}); - -ariaTest('key control+shift+end moves focus and selects all options', exampleFile, 'key-control-shift-end', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the first item, and selects item - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.END)); +); + +ariaTest( + 'key control+shift+home moves focus and selects all options', + exampleFile, + 'key-control-shift-home', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - t.is(selected, 'true', 'aria-selected should be true for all options after using shift + control + end from first option'); + // Put the focus on the last item, and selects item + await t.context.session + .findElement(By.css(ex[2].lastOptionSelector)) + .click(); + listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.HOME)); + + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + t.is( + selected, + 'true', + 'aria-selected should be true for all options after using shift + control + home from last option' + ); + } } -}); - -ariaTest('key control+shift+end moves focus and selects some options', exampleFile, 'key-control-shift-end', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the 3rd option, arrow down, then do end - await t.context.session.findElement(By.css(`${ex[2].optionSelector}:nth-child(3)`)).click(); - await listbox.sendKeys(Key.ARROW_DOWN); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.END)); +); + +ariaTest( + 'key control+shift+home moves focus and selects some options', + exampleFile, + 'key-control-shift-home', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // expect 4th - last options to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index >= 3; - t.is(selected, `${shouldBeSelected}`, 'aria-selected should be true for fourth through last options'); + // Put the focus on the 5th option, arrow up, then do home + await t.context.session + .findElement(By.css(`${ex[2].optionSelector}:nth-child(5)`)) + .click(); + await listbox.sendKeys(Key.ARROW_UP); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.HOME)); + + // expect 1st-4th options to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index < 4; + t.is( + selected, + `${shouldBeSelected}`, + 'aria-selected should be true for first through fourth options' + ); + } } -}); - -ariaTest('key shift+end does not change selection', exampleFile, 'key-control-shift-end', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // Put the focus on the first option and select it - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - await listbox.sendKeys(Key.chord(Key.SHIFT, Key.END)); +); + +ariaTest( + 'key shift+home does not change selection', + exampleFile, + 'key-control-shift-home', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // expect only first item to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - const shouldBeSelected = index === 0; - t.is(selected, `${shouldBeSelected}`, 'aria-selected should only be true for first option'); + // Put the focus on the last option and select it + await t.context.session + .findElement(By.css(ex[2].lastOptionSelector)) + .click(); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.HOME)); + + // expect only last item + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index === options.length - 1; + t.is( + selected, + `${shouldBeSelected}`, + 'aria-selected should only be true for last option' + ); + } } -}); - -ariaTest('key control+A selects all options', exampleFile, 'key-control-a', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // click inside listbox - await t.context.session.findElement(By.css(`${ex[2].optionSelector}:nth-child(2)`)).click(); - await listbox.sendKeys(Key.chord(Key.CONTROL, 'a')); +); + +ariaTest( + 'key control+shift+end moves focus and selects all options', + exampleFile, + 'key-control-shift-end', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // expect all items to be selected - for (let index = options.length - 1; index >= 0 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - t.is(selected, 'true', 'all options should be selected after using control + a'); + // Put the focus on the first item, and selects item + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.END)); + + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + t.is( + selected, + 'true', + 'aria-selected should be true for all options after using shift + control + end from first option' + ); + } } -}); +); + +ariaTest( + 'key control+shift+end moves focus and selects some options', + exampleFile, + 'key-control-shift-end', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); -ariaTest('A without control performs default focus move', exampleFile, 'key-control-a', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[2].listboxSelector)); - const options = await t.context.queryElements(t, ex[2].optionSelector); - - // get index of first option that begin with a - const matchingOptions = []; - for (let index = 0; index < options.length; index++) { - const optionText = await options[index].getText(); - if (optionText && optionText[0].toLowerCase() === 'a') { - matchingOptions.push(options[index]); + // Put the focus on the 3rd option, arrow down, then do end + await t.context.session + .findElement(By.css(`${ex[2].optionSelector}:nth-child(3)`)) + .click(); + await listbox.sendKeys(Key.ARROW_DOWN); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.CONTROL, Key.END)); + + // expect 4th - last options to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index >= 3; + t.is( + selected, + `${shouldBeSelected}`, + 'aria-selected should be true for fourth through last options' + ); } } - const matchingIndex = matchingOptions.length ? options.indexOf(matchingOptions[0]) : null; - - // click inside listbox - await t.context.session.findElement(By.css(ex[2].firstOptionSelector)).click(); - await listbox.sendKeys('a'); +); + +ariaTest( + 'key shift+end does not change selection', + exampleFile, + 'key-control-shift-end', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - await assertAriaActivedescendant(t, ex[2].listboxSelector, ex[2].optionSelector, matchingIndex); + // Put the focus on the first option and select it + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); + await listbox.sendKeys(Key.chord(Key.SHIFT, Key.END)); + + // expect only first item to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + const shouldBeSelected = index === 0; + t.is( + selected, + `${shouldBeSelected}`, + 'aria-selected should only be true for first option' + ); + } + } +); + +ariaTest( + 'key control+A selects all options', + exampleFile, + 'key-control-a', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); - // expect only first item to be selected - for (let index = options.length - 1; index >= 1 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - t.is(selected, 'false', 'a character alone should not select options'); + // click inside listbox + await t.context.session + .findElement(By.css(`${ex[2].optionSelector}:nth-child(2)`)) + .click(); + await listbox.sendKeys(Key.chord(Key.CONTROL, 'a')); + + // expect all items to be selected + for (let index = options.length - 1; index >= 0; index--) { + const selected = await options[index].getAttribute('aria-selected'); + t.is( + selected, + 'true', + 'all options should be selected after using control + a' + ); + } } -}); +); + +ariaTest( + 'A without control performs default focus move', + exampleFile, + 'key-control-a', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[2].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[2].optionSelector); + + // get index of first option that begin with a + const matchingOptions = []; + for (let index = 0; index < options.length; index++) { + const optionText = await options[index].getText(); + if (optionText && optionText[0].toLowerCase() === 'a') { + matchingOptions.push(options[index]); + } + } + const matchingIndex = matchingOptions.length + ? options.indexOf(matchingOptions[0]) + : null; -ariaTest('Control + A performs no action on single select listbox', exampleFile, 'key-control-a', async (t) => { - const listbox = await t.context.session.findElement(By.css(ex[1].listboxSelector)); - const options = await t.context.queryElements(t, ex[1].optionSelector); + // click inside listbox + await t.context.session + .findElement(By.css(ex[2].firstOptionSelector)) + .click(); + await listbox.sendKeys('a'); + + await assertAriaActivedescendant( + t, + ex[2].listboxSelector, + ex[2].optionSelector, + matchingIndex + ); - // click inside listbox - await t.context.session.findElement(By.css(ex[1].firstOptionSelector)).click(); - await listbox.sendKeys(Key.chord(Key.CONTROL, 'a')); + // expect only first item to be selected + for (let index = options.length - 1; index >= 1; index--) { + const selected = await options[index].getAttribute('aria-selected'); + t.is(selected, 'false', 'a character alone should not select options'); + } + } +); + +ariaTest( + 'Control + A performs no action on single select listbox', + exampleFile, + 'key-control-a', + async (t) => { + const listbox = await t.context.session.findElement( + By.css(ex[1].listboxSelector) + ); + const options = await t.context.queryElements(t, ex[1].optionSelector); - await assertAriaActivedescendant(t, ex[1].listboxSelector, ex[1].optionSelector, 0); + // click inside listbox + await t.context.session + .findElement(By.css(ex[1].firstOptionSelector)) + .click(); + await listbox.sendKeys(Key.chord(Key.CONTROL, 'a')); + + await assertAriaActivedescendant( + t, + ex[1].listboxSelector, + ex[1].optionSelector, + 0 + ); - // expect only first item to be selected - for (let index = options.length - 1; index >= 1 ; index--) { - const selected = await options[index].getAttribute('aria-selected'); - t.is(selected, null, 'control + a should not affect single select listbox'); + // expect only first item to be selected + for (let index = options.length - 1; index >= 1; index--) { + const selected = await options[index].getAttribute('aria-selected'); + t.is( + selected, + null, + 'control + a should not affect single select listbox' + ); + } } -}); - +); diff --git a/test/tests/listbox_scrollable.js b/test/tests/listbox_scrollable.js index 26525e9eaa..d1d98a4885 100644 --- a/test/tests/listbox_scrollable.js +++ b/test/tests/listbox_scrollable.js @@ -14,77 +14,123 @@ const ex = { listboxSelector: '#ex [role="listbox"]', optionSelector: '#ex [role="option"]', numOptions: 26, - firstOptionSelector: '#ex #ss_elem_Np' + firstOptionSelector: '#ex #ss_elem_Np', }; // Attributes -ariaTest('role="listbox" on ul element', exampleFile, 'listbox-role', async (t) => { +ariaTest( + 'role="listbox" on ul element', + exampleFile, + 'listbox-role', + async (t) => { await assertAriaRoles(t, 'ex', 'listbox', 1, 'ul'); -}); + } +); -ariaTest('"aria-labelledby" on listbox element', exampleFile, 'listbox-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on listbox element', + exampleFile, + 'listbox-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.listboxSelector); -}); + } +); -ariaTest('tabindex="0" on listbox element', exampleFile, 'listbox-tabindex', async (t) => { +ariaTest( + 'tabindex="0" on listbox element', + exampleFile, + 'listbox-tabindex', + async (t) => { await assertAttributeValues(t, ex.listboxSelector, 'tabindex', '0'); -}); - -ariaTest('aria-activedescendant on listbox element', exampleFile, 'listbox-aria-activedescendant', async (t) => { - - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); - - let options = await t.context.queryElements(t, ex.optionSelector); - let optionId = await options[0].getAttribute('id'); - - t.is( - await t.context.session - .findElement(By.css(ex.listboxSelector)) - .getAttribute('aria-activedescendant'), - optionId, - 'aria-activedescendant should be set to ' + optionId + ' for items: ' + ex.listboxSelector - ); -}); + } +); + +ariaTest( + 'aria-activedescendant on listbox element', + exampleFile, + 'listbox-aria-activedescendant', + async (t) => { + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. + await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); + + let options = await t.context.queryElements(t, ex.optionSelector); + let optionId = await options[0].getAttribute('id'); + + t.is( + await t.context.session + .findElement(By.css(ex.listboxSelector)) + .getAttribute('aria-activedescendant'), + optionId, + 'aria-activedescendant should be set to ' + + optionId + + ' for items: ' + + ex.listboxSelector + ); + } +); -ariaTest('role="option" on li elements', exampleFile, 'option-role', async (t) => { +ariaTest( + 'role="option" on li elements', + exampleFile, + 'option-role', + async (t) => { await assertAriaRoles(t, 'ex', 'option', 26, 'li'); -}); - -ariaTest('"aria-selected" on option elements', exampleFile, 'option-aria-selected', async (t) => { - - await assertAttributeDNE(t, ex.optionSelector, 'aria-selected'); - - // Put the focus on the listbox. In this example, focusing on the listbox - // will automatically select the first option. - await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); - - await assertAttributeValues(t, ex.optionSelector + ':nth-child(1)', 'aria-selected', 'true'); -}); + } +); + +ariaTest( + '"aria-selected" on option elements', + exampleFile, + 'option-aria-selected', + async (t) => { + await assertAttributeDNE(t, ex.optionSelector, 'aria-selected'); + + // Put the focus on the listbox. In this example, focusing on the listbox + // will automatically select the first option. + await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); + + await assertAttributeValues( + t, + ex.optionSelector + ':nth-child(1)', + 'aria-selected', + 'true' + ); + } +); // Keys ariaTest('DOWN ARROW moves focus', exampleFile, 'key-down-arrow', async (t) => { - // Put the focus on the listbox. In this example, focusing on the listbox // will automatically select the first option. await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); // Sending the key down arrow will put focus on the item at index 1 - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); await listbox.sendKeys(Key.ARROW_DOWN); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 1 + ); // The selection does not wrap to beginning of list if keydown arrow is sent more times // then their are options for (let i = 0; i < ex.numOptions + 1; i++) { await listbox.sendKeys(Key.ARROW_DOWN); } - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 1 + ); }); ariaTest('END moves focus', exampleFile, 'key-end', async (t) => { @@ -94,57 +140,88 @@ ariaTest('END moves focus', exampleFile, 'key-end', async (t) => { // will automatically select the first option. await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); // Sending key end should put focus on the last item await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 1 + ); // Sending key end twice should put focus on the last item await listbox.sendKeys(Key.END); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 1); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 1 + ); }); ariaTest('UP ARROW moves focus', exampleFile, 'key-up-arrow', async (t) => { - // Put the focus on the listbox. In this example, focusing on the listbox // will automatically select the first option. await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); // Sending key end should put focus on the last item await listbox.sendKeys(Key.END); // Sending the key up arrow will put focus on the item at index numOptions-2 await listbox.sendKeys(Key.ARROW_UP); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, - ex.numOptions - 2); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + ex.numOptions - 2 + ); // The selection does not wrap to the bottom of list if key up arrow is sent more times // then their are options for (let i = 0; i < ex.numOptions + 1; i++) { await listbox.sendKeys(Key.ARROW_UP); } - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); }); ariaTest('HOME moves focus', exampleFile, 'key-home', async (t) => { - // Put the focus on the listbox. In this example, focusing on the listbox // will automatically select the first option. await t.context.session.findElement(By.css(ex.firstOptionSelector)).click(); - const listbox = await t.context.session.findElement(By.css(ex.listboxSelector)); + const listbox = await t.context.session.findElement( + By.css(ex.listboxSelector) + ); await listbox.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN); // Sending key home should always put focus on the first item await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); // Sending key home twice should always put focus on the first item await listbox.sendKeys(Key.HOME); - await assertAriaSelectedAndActivedescendant(t, ex.listboxSelector, ex.optionSelector, 0); + await assertAriaSelectedAndActivedescendant( + t, + ex.listboxSelector, + ex.optionSelector, + 0 + ); }); diff --git a/test/tests/menu-button_actions-active-descendant.js b/test/tests/menu-button_actions-active-descendant.js index fd01cf5118..1e3a95f3ee 100644 --- a/test/tests/menu-button_actions-active-descendant.js +++ b/test/tests/menu-button_actions-active-descendant.js @@ -16,147 +16,249 @@ const ex = { menuitemSelector: '#ex1 [role="menuitem"]', numMenuitems: 4, lastactionSelector: '#action_output', - defaultAriaActivedescendantVal: 'mi1' + defaultAriaActivedescendantVal: 'mi1', }; const checkFocus = function (t, selector, index) { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const openMenu = async function (t) { - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .click(); - - return t.context.session.wait(async function () { - return t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(); - }, t.context.waitTime, 'Timeout waiting for menu to open after click'); + await t.context.session.findElement(By.css(ex.menubuttonSelector)).click(); + + return t.context.session.wait( + async function () { + return t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(); + }, + t.context.waitTime, + 'Timeout waiting for menu to open after click' + ); }; // Attributes -ariaTest('"aria-haspopup" attribute on menu button', exampleFile, 'button-aria-haspopup', async (t) => { - await assertAttributeValues(t, ex.menubuttonSelector, 'aria-haspopup', 'true'); -}); +ariaTest( + '"aria-haspopup" attribute on menu button', + exampleFile, + 'button-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.menubuttonSelector, + 'aria-haspopup', + 'true' + ); + } +); -ariaTest('"aria-controls" attribute on menu button', exampleFile, 'button-aria-controls', async (t) => { +ariaTest( + '"aria-controls" attribute on menu button', + exampleFile, + 'button-aria-controls', + async (t) => { await assertAriaControls(t, ex.menubuttonSelector); -}); - -ariaTest('"aria-expanded" attribute on menu button', exampleFile, 'button-aria-expanded', async (t) => { - - const hasAttribute = await t.context.session.executeScript(function () { - selector = arguments[0]; - return document.querySelector(selector).hasAttribute('aria-expanded'); - }, ex.menubuttonSelector); - - t.false( - hasAttribute, - 'The menuitem should not have the "aria-expanded" attribute if the popup is closed' - ); + } +); + +ariaTest( + '"aria-expanded" attribute on menu button', + exampleFile, + 'button-aria-expanded', + async (t) => { + const hasAttribute = await t.context.session.executeScript(function () { + selector = arguments[0]; + return document.querySelector(selector).hasAttribute('aria-expanded'); + }, ex.menubuttonSelector); - t.false( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should not be displayed if aria-expanded is false' - ); + t.false( + hasAttribute, + 'The menuitem should not have the "aria-expanded" attribute if the popup is closed' + ); - await openMenu(t); + t.false( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should not be displayed if aria-expanded is false' + ); - await assertAttributeValues(t, ex.menubuttonSelector, 'aria-expanded', 'true'); - t.true( - await t.context.session.findElement(By.css(ex.menuitemSelector)).isDisplayed(), - 'The popup should be displayed if aria-expanded is true' - ); + await openMenu(t); -}); + await assertAttributeValues( + t, + ex.menubuttonSelector, + 'aria-expanded', + 'true' + ); + t.true( + await t.context.session + .findElement(By.css(ex.menuitemSelector)) + .isDisplayed(), + 'The popup should be displayed if aria-expanded is true' + ); + } +); ariaTest('role="menu" on ul element', exampleFile, 'menu-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'menu', 1, 'ul'); + await assertAriaRoles(t, 'ex1', 'menu', 1, 'ul'); }); -ariaTest('"aria-labelledby" on role="menu"', exampleFile, 'menu-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on role="menu"', + exampleFile, + 'menu-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.menuSelector); -}); + } +); // This test fails due to bug: https://github.com/w3c/aria-practices/issues/894 -ariaTest.failing('tabindex="-1" on role="menu"', exampleFile, 'menu-tabindex', async (t) => { +ariaTest.failing( + 'tabindex="-1" on role="menu"', + exampleFile, + 'menu-tabindex', + async (t) => { await openMenu(t); - await assertAttributeValues(t, ex.menuSelector, 'tabindex', '-1'); -}); - -ariaTest('aria-activedescendant on role="menu"', exampleFile, 'menu-aria-activedescendant', async (t) => { - await assertAttributeValues(t, ex.menuSelector, 'aria-activedescendant', ex.defaultAriaActivedescendantVal); -}); + await assertAttributeValues(t, ex.menuSelector, 'tabindex', '-1'); + } +); + +ariaTest( + 'aria-activedescendant on role="menu"', + exampleFile, + 'menu-aria-activedescendant', + async (t) => { + await assertAttributeValues( + t, + ex.menuSelector, + 'aria-activedescendant', + ex.defaultAriaActivedescendantVal + ); + } +); -ariaTest('role="menuitem" on li element', exampleFile, 'menuitem-role', async (t) => { +ariaTest( + 'role="menuitem" on li element', + exampleFile, + 'menuitem-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'menuitem', ex.numMenuitems, 'li'); -}); - + } +); // Keys -ariaTest('"enter" on menu button', exampleFile, 'button-down-arrow-or-space-or-enter', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ENTER); +ariaTest( + '"enter" on menu button', + exampleFile, + 'button-down-arrow-or-space-or-enter', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ENTER); - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button ENTER' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ENTER' + ); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, 0); -}); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + 0 + ); + } +); -ariaTest('"down arrow" on menu button', exampleFile, 'button-down-arrow-or-space-or-enter', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ARROW_DOWN); +ariaTest( + '"down arrow" on menu button', + exampleFile, + 'button-down-arrow-or-space-or-enter', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ARROW_DOWN); - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button ARROW_DOWN' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ARROW_DOWN' + ); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, 0); -}); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + 0 + ); + } +); -ariaTest('"space" on menu button', exampleFile, 'button-down-arrow-or-space-or-enter', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.SPACE); +ariaTest( + '"space" on menu button', + exampleFile, + 'button-down-arrow-or-space-or-enter', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.SPACE); - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button SPACE' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button SPACE' + ); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, 0); -}); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + 0 + ); + } +); -ariaTest('"up arrow" on menu button', exampleFile, 'button-up-arrow', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ARROW_UP); +ariaTest( + '"up arrow" on menu button', + exampleFile, + 'button-up-arrow', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ARROW_UP); - t.true( - await t.context.session.findElement(By.css(ex.menuitemSelector)).isDisplayed(), - 'The popup should be displayed after sending button ARROW_UP' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuitemSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ARROW_UP' + ); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, ex.numMenuitems - 1); -}); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + ex.numMenuitems - 1 + ); + } +); ariaTest('"enter" on role="menu"', exampleFile, 'menu-enter', async (t) => { - const menu = await t.context.session.findElement(By.css(ex.menuSelector)); const items = await t.context.queryElements(t, ex.menuitemSelector); @@ -168,7 +270,9 @@ ariaTest('"enter" on role="menu"', exampleFile, 'menu-enter', async (t) => { t.is( itemText, - await t.context.session.findElement(By.css(ex.lastactionSelector)).getAttribute('value'), + await t.context.session + .findElement(By.css(ex.lastactionSelector)) + .getAttribute('value'), 'When first item is focused, key enter should select action: ' + itemText ); @@ -190,7 +294,9 @@ ariaTest('"enter" on role="menu"', exampleFile, 'menu-enter', async (t) => { t.is( itemText, - await t.context.session.findElement(By.css(ex.lastactionSelector)).getAttribute('value'), + await t.context.session + .findElement(By.css(ex.lastactionSelector)) + .getAttribute('value'), 'When second item is focused, key enter should select action: ' + itemText ); @@ -212,7 +318,9 @@ ariaTest('"enter" on role="menu"', exampleFile, 'menu-enter', async (t) => { t.is( itemText, - await t.context.session.findElement(By.css(ex.lastactionSelector)).getAttribute('value'), + await t.context.session + .findElement(By.css(ex.lastactionSelector)) + .getAttribute('value'), 'When third item is focused, key enter should select action: ' + itemText ); @@ -230,11 +338,18 @@ ariaTest('"enter" on role="menu"', exampleFile, 'menu-enter', async (t) => { await openMenu(t); itemText = await items[3].getText(); - await menu.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN, Key.ARROW_DOWN, Key.ENTER); + await menu.sendKeys( + Key.ARROW_DOWN, + Key.ARROW_DOWN, + Key.ARROW_DOWN, + Key.ENTER + ); t.is( itemText, - await t.context.session.findElement(By.css(ex.lastactionSelector)).getAttribute('value'), + await t.context.session + .findElement(By.css(ex.lastactionSelector)) + .getAttribute('value'), 'When fourth item is focused, key enter should select action: ' + itemText ); @@ -250,23 +365,25 @@ ariaTest('"enter" on role="menu"', exampleFile, 'menu-enter', async (t) => { }); ariaTest('"escape" on role="menu"', exampleFile, 'menu-escape', async (t) => { - const menu = await t.context.session.findElement(By.css(ex.menuSelector)); const items = await t.context.queryElements(t, ex.menuitemSelector); for (let item of items) { - await openMenu(t); const itemText = await item.getText(); await item.sendKeys(Key.ESCAPE); t.not( itemText, - await t.context.session.findElement(By.css(ex.lastactionSelector)).getAttribute('value'), + await t.context.session + .findElement(By.css(ex.lastactionSelector)) + .getAttribute('value'), 'Key escape should not select action: ' + itemText ); t.false( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), 'Key escape on item "' + itemText + '" should close menu.' ); @@ -280,7 +397,7 @@ ariaTest('"escape" on role="menu"', exampleFile, 'menu-escape', async (t) => { // This test is flaky, so is commented out for now. // We are traking it in issue:https://github.com/w3c/aria-practices/issues/1415 // ariaTest('"down arrow" on role="menu"', exampleFile, 'menu-down-arrow', async (t) => { - + // await openMenu(t); // const menu = await t.context.session.findElement(By.css(ex.menuSelector)); // const items = await t.context.queryElements(t, ex.menuitemSelector); @@ -298,7 +415,7 @@ ariaTest('"escape" on role="menu"', exampleFile, 'menu-escape', async (t) => { // This test is flaky, so is commented out for now. // We are traking it in issue:https://github.com/w3c/aria-practices/issues/1415 // ariaTest('"up arrow" on role="menu"', exampleFile, 'menu-up-arrow', async (t) => { - + // await openMenu(t); // const menu = await t.context.session.findElement(By.css(ex.menuSelector)); // const items = await t.context.queryElements(t, ex.menuitemSelector); @@ -315,7 +432,6 @@ ariaTest('"escape" on role="menu"', exampleFile, 'menu-escape', async (t) => { // }); ariaTest('"home" on role="menu"', exampleFile, 'menu-home', async (t) => { - const menu = await t.context.session.findElement(By.css(ex.menuSelector)); const items = await t.context.queryElements(t, ex.menuitemSelector); await openMenu(t); @@ -342,31 +458,50 @@ ariaTest('"home" on role="menu"', exampleFile, 'menu-home', async (t) => { }); ariaTest('"end" on role="menu"', exampleFile, 'menu-end', async (t) => { - const menu = await t.context.session.findElement(By.css(ex.menuSelector)); const items = await t.context.queryElements(t, ex.menuitemSelector); - const last = ex.numMenuitems - 1; + const last = ex.numMenuitems - 1; await openMenu(t); // Send END to the menu while aria-activedescendant is the first item await menu.sendKeys(Key.END); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, last); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + last + ); // Send END to the menu while aria-activedescendant is the second item await menu.sendKeys(Key.ARROW_DOWN, Key.END); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, last); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + last + ); // Send END to the menu while aria-activedescendant is the third item await menu.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN, Key.END); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, last); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + last + ); // Send END to the menu while aria-activedescendant is the fourth item await menu.sendKeys(Key.ARROW_DOWN, Key.ARROW_DOWN, Key.ARROW_DOWN, Key.END); - await assertAriaActivedescendant(t, ex.menuSelector, ex.menuitemSelector, last); + await assertAriaActivedescendant( + t, + ex.menuSelector, + ex.menuitemSelector, + last + ); }); // This test is flaky, so is commented out for now. diff --git a/test/tests/menu-button_actions.js b/test/tests/menu-button_actions.js index 593986caa9..da827c67d0 100644 --- a/test/tests/menu-button_actions.js +++ b/test/tests/menu-button_actions.js @@ -14,279 +14,370 @@ const ex = { menuSelector: '#ex1 [role="menu"]', menuitemSelector: '#ex1 [role="menuitem"]', numMenuitems: 4, - lastactionSelector: '#action_output' + lastactionSelector: '#action_output', }; const checkFocus = function (t, selector, index) { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - let items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + let items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const openMenu = async function (t) { - return t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .click(); + return t.context.session.findElement(By.css(ex.menubuttonSelector)).click(); }; // Attributes -ariaTest('"aria-haspopup" attribute on menu button', exampleFile, 'menu-button-aria-haspopup', async (t) => { - await assertAttributeValues(t, ex.menubuttonSelector, 'aria-haspopup', 'true'); -}); +ariaTest( + '"aria-haspopup" attribute on menu button', + exampleFile, + 'menu-button-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.menubuttonSelector, + 'aria-haspopup', + 'true' + ); + } +); -ariaTest('"aria-controls" attribute on menu button', exampleFile, 'menu-button-aria-controls', async (t) => { +ariaTest( + '"aria-controls" attribute on menu button', + exampleFile, + 'menu-button-aria-controls', + async (t) => { await assertAriaControls(t, ex.menubuttonSelector); -}); - -ariaTest('"aria-expanded" attribute on menu button', exampleFile, 'menu-button-aria-expanded', async (t) => { - - const hasAttribute = await t.context.session.executeScript(function () { - selector = arguments[0]; - return document.querySelector(selector).hasAttribute('aria-expanded'); - }, ex.menubuttonSelector); - - t.false( - hasAttribute, - 'The menuitem should not have the "aria-expanded" attribute if the popup is closed' - ); + } +); + +ariaTest( + '"aria-expanded" attribute on menu button', + exampleFile, + 'menu-button-aria-expanded', + async (t) => { + const hasAttribute = await t.context.session.executeScript(function () { + selector = arguments[0]; + return document.querySelector(selector).hasAttribute('aria-expanded'); + }, ex.menubuttonSelector); - t.false( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should not be displayed if aria-expanded is false' - ); + t.false( + hasAttribute, + 'The menuitem should not have the "aria-expanded" attribute if the popup is closed' + ); - await openMenu(t); + t.false( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should not be displayed if aria-expanded is false' + ); - await assertAttributeValues(t, ex.menubuttonSelector, 'aria-expanded', 'true'); - t.true( - await t.context.session.findElement(By.css(ex.menuitemSelector)).isDisplayed(), - 'The popup should be displayed if aria-expanded is true' - ); + await openMenu(t); -}); + await assertAttributeValues( + t, + ex.menubuttonSelector, + 'aria-expanded', + 'true' + ); + t.true( + await t.context.session + .findElement(By.css(ex.menuitemSelector)) + .isDisplayed(), + 'The popup should be displayed if aria-expanded is true' + ); + } +); ariaTest('role="menu" on ul element', exampleFile, 'menu-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'menu', 1, 'ul'); + await assertAriaRoles(t, 'ex1', 'menu', 1, 'ul'); }); -ariaTest('"aria-labelledby" on role="menu"', exampleFile, 'menu-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on role="menu"', + exampleFile, + 'menu-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.menuSelector); -}); + } +); -ariaTest('role="menuitem" on li element', exampleFile, 'menuitem-role', async (t) => { +ariaTest( + 'role="menuitem" on li element', + exampleFile, + 'menuitem-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'menuitem', ex.numMenuitems, 'li'); -}); + } +); -ariaTest('tabindex="-1" on role="menuitem"', exampleFile, 'menuitem-tabindex', async (t) => { +ariaTest( + 'tabindex="-1" on role="menuitem"', + exampleFile, + 'menuitem-tabindex', + async (t) => { await assertAttributeValues(t, ex.menuitemSelector, 'tabindex', '-1'); -}); - + } +); // Keys -ariaTest('"enter" on menu button', exampleFile, 'menu-button-key-open', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ENTER); +ariaTest( + '"enter" on menu button', + exampleFile, + 'menu-button-key-open', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ENTER); - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button ENTER' - ); - - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'Focus should be on first item after sending button ENTER' - ); -}); - -ariaTest('"down arrow" on menu button', exampleFile, 'menu-button-key-open', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ARROW_DOWN); - - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button ARROW_DOWN' - ); - - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'Focus should be on first item after sending button ARROW_DOWN' - ); -}); - -ariaTest('"space" on menu button', exampleFile, 'menu-button-key-open', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.SPACE); - - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button SPACE' - ); - - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'Focus should be on first item after sending button SPACE' - ); -}); - -ariaTest('"up arrow" on menu button', exampleFile, 'menu-button-key-up-arrow', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ARROW_UP); - - t.true( - await t.context.session.findElement(By.css(ex.menuitemSelector)).isDisplayed(), - 'The popup should be displayed after sending button ARROW_UP' - ); - - t.true( - await checkFocus(t, ex.menuitemSelector, ex.numMenuitems - 1), - 'Focus should be on last item after sending button ARROW_UP' - ); -}); - -ariaTest('"enter" on role="menuitem"', exampleFile, 'menu-key-enter', async (t) => { - - const items = await t.context.queryElements(t, ex.menuitemSelector); - for (let item of items) { - - await openMenu(t); - const itemText = await item.getText(); - await item.sendKeys(Key.ENTER); - - t.is( - itemText, - await t.context.session.findElement(By.css(ex.lastactionSelector)).getAttribute('value'), - 'Key enter should select action: ' + itemText - ); - - t.false( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'Key enter on item "' + itemText + '" should close menu.' + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ENTER' ); t.true( - await checkFocus(t, ex.menubuttonSelector, 0), - 'Key enter on item "' + itemText + '" should put focus back on menu.' + await checkFocus(t, ex.menuitemSelector, 0), + 'Focus should be on first item after sending button ENTER' ); } -}); - -ariaTest('"escape" on role="menuitem"', exampleFile, 'menu-key-escape', async (t) => { - - const items = await t.context.queryElements(t, ex.menuitemSelector); - for (let item of items) { - - await openMenu(t); - const itemText = await item.getText(); - await item.sendKeys(Key.ESCAPE); +); - t.not( - itemText, - await t.context.session.findElement(By.css(ex.lastactionSelector)).getAttribute('value'), - 'Key escape should not select action: ' + itemText - ); +ariaTest( + '"down arrow" on menu button', + exampleFile, + 'menu-button-key-open', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ARROW_DOWN); - t.false( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'Key escape on item "' + itemText + '" should close menu.' + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ARROW_DOWN' ); t.true( - await checkFocus(t, ex.menubuttonSelector, 0), - 'Key escape on item "' + itemText + '" should put focus back on menu.' + await checkFocus(t, ex.menuitemSelector, 0), + 'Focus should be on first item after sending button ARROW_DOWN' ); } -}); +); -ariaTest('"down arrow" on role="menuitem"', exampleFile, 'menu-key-down-arrow', async (t) => { - - await openMenu(t); - - const items = await t.context.queryElements(t, ex.menuitemSelector); - for (let index = 0; index < items.length - 1; index++) { +ariaTest( + '"space" on menu button', + exampleFile, + 'menu-button-key-open', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.SPACE); - await items[index].sendKeys(Key.ARROW_DOWN); + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button SPACE' + ); - const itemText = await items[index].getText(); t.true( - await checkFocus(t, ex.menuitemSelector, index + 1), - 'down arrow on item "' + itemText + '" should put focus on the next item.' + await checkFocus(t, ex.menuitemSelector, 0), + 'Focus should be on first item after sending button SPACE' ); } +); - await items[items.length - 1].sendKeys(Key.ARROW_DOWN); - - const itemText = await items[items.length - 1].getText(); - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'down arrow on item "' + itemText + '" should put focus to first item.' - ); - -}); +ariaTest( + '"up arrow" on menu button', + exampleFile, + 'menu-button-key-up-arrow', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ARROW_UP); -ariaTest('"up arrow" on role="menuitem"', exampleFile, 'menu-key-up-arrow', async (t) => { - - await openMenu(t); + t.true( + await t.context.session + .findElement(By.css(ex.menuitemSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ARROW_UP' + ); - const items = await t.context.queryElements(t, ex.menuitemSelector); + t.true( + await checkFocus(t, ex.menuitemSelector, ex.numMenuitems - 1), + 'Focus should be on last item after sending button ARROW_UP' + ); + } +); + +ariaTest( + '"enter" on role="menuitem"', + exampleFile, + 'menu-key-enter', + async (t) => { + const items = await t.context.queryElements(t, ex.menuitemSelector); + for (let item of items) { + await openMenu(t); + const itemText = await item.getText(); + await item.sendKeys(Key.ENTER); + + t.is( + itemText, + await t.context.session + .findElement(By.css(ex.lastactionSelector)) + .getAttribute('value'), + 'Key enter should select action: ' + itemText + ); + + t.false( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'Key enter on item "' + itemText + '" should close menu.' + ); + + t.true( + await checkFocus(t, ex.menubuttonSelector, 0), + 'Key enter on item "' + itemText + '" should put focus back on menu.' + ); + } + } +); + +ariaTest( + '"escape" on role="menuitem"', + exampleFile, + 'menu-key-escape', + async (t) => { + const items = await t.context.queryElements(t, ex.menuitemSelector); + for (let item of items) { + await openMenu(t); + const itemText = await item.getText(); + await item.sendKeys(Key.ESCAPE); + + t.not( + itemText, + await t.context.session + .findElement(By.css(ex.lastactionSelector)) + .getAttribute('value'), + 'Key escape should not select action: ' + itemText + ); + + t.false( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'Key escape on item "' + itemText + '" should close menu.' + ); + + t.true( + await checkFocus(t, ex.menubuttonSelector, 0), + 'Key escape on item "' + itemText + '" should put focus back on menu.' + ); + } + } +); - await items[0].sendKeys(Key.ARROW_UP); +ariaTest( + '"down arrow" on role="menuitem"', + exampleFile, + 'menu-key-down-arrow', + async (t) => { + await openMenu(t); - const itemText = await items[0].getText(); - t.true( - await checkFocus(t, ex.menuitemSelector, items.length - 1), - 'up arrow on item "' + itemText + '" should put focus to last item.' - ); + const items = await t.context.queryElements(t, ex.menuitemSelector); + for (let index = 0; index < items.length - 1; index++) { + await items[index].sendKeys(Key.ARROW_DOWN); - for (let index = items.length - 1; index > 0; index--) { + const itemText = await items[index].getText(); + t.true( + await checkFocus(t, ex.menuitemSelector, index + 1), + 'down arrow on item "' + + itemText + + '" should put focus on the next item.' + ); + } - await items[index].sendKeys(Key.ARROW_UP); + await items[items.length - 1].sendKeys(Key.ARROW_DOWN); - const itemText = await items[index].getText(); + const itemText = await items[items.length - 1].getText(); t.true( - await checkFocus(t, ex.menuitemSelector, index - 1), - 'down arrow on item "' + itemText + '" should put focus on the previous item.' + await checkFocus(t, ex.menuitemSelector, 0), + 'down arrow on item "' + itemText + '" should put focus to first item.' ); } +); -}); - -ariaTest('"home" on role="menuitem"', exampleFile, 'menu-key-home', async (t) => { - - await openMenu(t); +ariaTest( + '"up arrow" on role="menuitem"', + exampleFile, + 'menu-key-up-arrow', + async (t) => { + await openMenu(t); - const items = await t.context.queryElements(t, ex.menuitemSelector); - for (let index = 0; index < items.length; index++) { + const items = await t.context.queryElements(t, ex.menuitemSelector); - await items[index].sendKeys(Key.HOME); + await items[0].sendKeys(Key.ARROW_UP); - const itemText = await items[index].getText(); + const itemText = await items[0].getText(); t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'key home on item "' + itemText + '" should put focus on the first time.' + await checkFocus(t, ex.menuitemSelector, items.length - 1), + 'up arrow on item "' + itemText + '" should put focus to last item.' ); + + for (let index = items.length - 1; index > 0; index--) { + await items[index].sendKeys(Key.ARROW_UP); + + const itemText = await items[index].getText(); + t.true( + await checkFocus(t, ex.menuitemSelector, index - 1), + 'down arrow on item "' + + itemText + + '" should put focus on the previous item.' + ); + } } +); -}); +ariaTest( + '"home" on role="menuitem"', + exampleFile, + 'menu-key-home', + async (t) => { + await openMenu(t); + + const items = await t.context.queryElements(t, ex.menuitemSelector); + for (let index = 0; index < items.length; index++) { + await items[index].sendKeys(Key.HOME); + + const itemText = await items[index].getText(); + t.true( + await checkFocus(t, ex.menuitemSelector, 0), + 'key home on item "' + + itemText + + '" should put focus on the first time.' + ); + } + } +); ariaTest('"end" on role="menuitem"', exampleFile, 'menu-key-end', async (t) => { - await openMenu(t); const items = await t.context.queryElements(t, ex.menuitemSelector); for (let index = 0; index < items.length; index++) { - await items[index].sendKeys(Key.END); const itemText = await items[index].getText(); @@ -297,25 +388,33 @@ ariaTest('"end" on role="menuitem"', exampleFile, 'menu-key-end', async (t) => { } }); -ariaTest('"character" on role="menuitem"', exampleFile, 'menu-key-character', async (t) => { - - const charIndexTest = [ - { sendChar: 'x', sendIndex: 0, endIndex: 0 }, - { sendChar: 'a', sendIndex: 0, endIndex: 1 }, - { sendChar: 'y', sendIndex: 1, endIndex: 1 }, - { sendChar: 'a', sendIndex: 1, endIndex: 2 } - ]; - - await openMenu(t); - const items = await t.context.queryElements(t, ex.menuitemSelector); +ariaTest( + '"character" on role="menuitem"', + exampleFile, + 'menu-key-character', + async (t) => { + const charIndexTest = [ + { sendChar: 'x', sendIndex: 0, endIndex: 0 }, + { sendChar: 'a', sendIndex: 0, endIndex: 1 }, + { sendChar: 'y', sendIndex: 1, endIndex: 1 }, + { sendChar: 'a', sendIndex: 1, endIndex: 2 }, + ]; - for (let test of charIndexTest) { - await items[test.sendIndex].sendKeys(test.sendChar); - - t.true( - await checkFocus(t, ex.menuitemSelector, test.endIndex), - 'Sending character "' + test.sendChar + '" to item at index ' + test.sendIndex + - '" should put focus on item at index: ' + test.endIndex - ); + await openMenu(t); + const items = await t.context.queryElements(t, ex.menuitemSelector); + + for (let test of charIndexTest) { + await items[test.sendIndex].sendKeys(test.sendChar); + + t.true( + await checkFocus(t, ex.menuitemSelector, test.endIndex), + 'Sending character "' + + test.sendChar + + '" to item at index ' + + test.sendIndex + + '" should put focus on item at index: ' + + test.endIndex + ); + } } -}); +); diff --git a/test/tests/menu-button_links.js b/test/tests/menu-button_links.js index b92dc52b99..81e616c518 100644 --- a/test/tests/menu-button_links.js +++ b/test/tests/menu-button_links.js @@ -13,15 +13,19 @@ const ex = { menubuttonSelector: '#ex1 button', menuSelector: '#ex1 [role="menu"]', menuitemSelector: '#ex1 [role="menuitem"]', - numMenuitems: 6 + numMenuitems: 6, }; const checkFocus = function (t, selector, index) { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - let items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + let items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const openMenu = async function (t) { @@ -30,166 +34,240 @@ const openMenu = async function (t) { .getAttribute('aria-expanded'); if (expanded !== 'true') { - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .click(); + await t.context.session.findElement(By.css(ex.menubuttonSelector)).click(); } - return t.context.session.wait(async function () { - return t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(); - }, t.context.waitTime, 'Timeout waiting for menu to open after click'); + return t.context.session.wait( + async function () { + return t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(); + }, + t.context.waitTime, + 'Timeout waiting for menu to open after click' + ); }; const waitForUrlChange = async function (t) { - await t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime, 'Timeout waiting for url to update'); + await t.context.session.wait( + () => { + return t.context.session.getCurrentUrl().then((url) => { + return url != t.context.url; + }); + }, + t.context.waitTime, + 'Timeout waiting for url to update' + ); }; const waitForNoAriaExpanded = async function (t) { - - return t.context.session.wait(async function () { - let ariaExpanded = await t.context.session.findElement(By.css(ex.menuSelector)) - .getAttribute('aria-expanded'); - return ariaExpanded === null; - }, t.context.waitTime, 'Timeout waiting for aria-expanded to be removed'); + return t.context.session.wait( + async function () { + let ariaExpanded = await t.context.session + .findElement(By.css(ex.menuSelector)) + .getAttribute('aria-expanded'); + return ariaExpanded === null; + }, + t.context.waitTime, + 'Timeout waiting for aria-expanded to be removed' + ); }; - // Attributes -ariaTest('"aria-haspopup" attribute on menu button', exampleFile, 'button-aria-haspopup', async (t) => { - await assertAttributeValues(t, ex.menubuttonSelector, 'aria-haspopup', 'true'); -}); +ariaTest( + '"aria-haspopup" attribute on menu button', + exampleFile, + 'button-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.menubuttonSelector, + 'aria-haspopup', + 'true' + ); + } +); -ariaTest('"aria-controls" attribute on menu button', exampleFile, 'button-aria-controls', async (t) => { +ariaTest( + '"aria-controls" attribute on menu button', + exampleFile, + 'button-aria-controls', + async (t) => { await assertAriaControls(t, ex.menubuttonSelector); -}); - -ariaTest('"aria-expanded" attribute on menu button', exampleFile, 'button-aria-expanded', async (t) => { - - const hasAttribute = await t.context.session.executeScript(function () { - selector = arguments[0]; - return document.querySelector(selector).hasAttribute('aria-expanded'); - }, ex.menubuttonSelector); - - t.false( - hasAttribute, - 'The menuitem should not have the "aria-expanded" attribute if the popup is closed' - ); - - t.false( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should not be displayed if aria-expanded is false' - ); + } +); + +ariaTest( + '"aria-expanded" attribute on menu button', + exampleFile, + 'button-aria-expanded', + async (t) => { + const hasAttribute = await t.context.session.executeScript(function () { + selector = arguments[0]; + return document.querySelector(selector).hasAttribute('aria-expanded'); + }, ex.menubuttonSelector); + + t.false( + hasAttribute, + 'The menuitem should not have the "aria-expanded" attribute if the popup is closed' + ); - await openMenu(t); + t.false( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should not be displayed if aria-expanded is false' + ); - await assertAttributeValues(t, ex.menubuttonSelector, 'aria-expanded', 'true'); - t.true( - await t.context.session.findElement(By.css(ex.menuitemSelector)).isDisplayed(), - 'The popup should be displayed if aria-expanded is true' - ); + await openMenu(t); -}); + await assertAttributeValues( + t, + ex.menubuttonSelector, + 'aria-expanded', + 'true' + ); + t.true( + await t.context.session + .findElement(By.css(ex.menuitemSelector)) + .isDisplayed(), + 'The popup should be displayed if aria-expanded is true' + ); + } +); ariaTest('role="menu" on ul element', exampleFile, 'menu-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'menu', 1, 'ul'); + await assertAriaRoles(t, 'ex1', 'menu', 1, 'ul'); }); -ariaTest('"aria-labelledby" on role="menu"', exampleFile, 'menu-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" on role="menu"', + exampleFile, + 'menu-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.menuSelector); -}); + } +); ariaTest('role="none" on li element', exampleFile, 'none-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'none', ex.numMenuitems, 'li'); + await assertAriaRoles(t, 'ex1', 'none', ex.numMenuitems, 'li'); }); -ariaTest('role="menuitem" on a element', exampleFile, 'menuitem-role', async (t) => { +ariaTest( + 'role="menuitem" on a element', + exampleFile, + 'menuitem-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'menuitem', ex.numMenuitems, 'a'); -}); + } +); -ariaTest('tabindex="-1" on role="menuitem"', exampleFile, 'menuitem-tabindex', async (t) => { +ariaTest( + 'tabindex="-1" on role="menuitem"', + exampleFile, + 'menuitem-tabindex', + async (t) => { await assertAttributeValues(t, ex.menuitemSelector, 'tabindex', '-1'); -}); - + } +); // Keys -ariaTest('"enter" on menu button', exampleFile, 'button-down-arrow-or-space-or-enter', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ENTER); +ariaTest( + '"enter" on menu button', + exampleFile, + 'button-down-arrow-or-space-or-enter', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ENTER); - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button ENTER' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ENTER' + ); - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'Focus should be on first item after sending button ENTER' - ); -}); + t.true( + await checkFocus(t, ex.menuitemSelector, 0), + 'Focus should be on first item after sending button ENTER' + ); + } +); -ariaTest('"down arrow" on menu button', exampleFile, 'button-down-arrow-or-space-or-enter', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ARROW_DOWN); +ariaTest( + '"down arrow" on menu button', + exampleFile, + 'button-down-arrow-or-space-or-enter', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ARROW_DOWN); - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button ARROW_DOWN' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ARROW_DOWN' + ); - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'Focus should be on first item after sending button ARROW_DOWN' - ); -}); + t.true( + await checkFocus(t, ex.menuitemSelector, 0), + 'Focus should be on first item after sending button ARROW_DOWN' + ); + } +); -ariaTest('"space" on menu button', exampleFile, 'button-down-arrow-or-space-or-enter', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.SPACE); +ariaTest( + '"space" on menu button', + exampleFile, + 'button-down-arrow-or-space-or-enter', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.SPACE); - t.true( - await t.context.session.findElement(By.css(ex.menuSelector)).isDisplayed(), - 'The popup should be displayed after sending button SPACE' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button SPACE' + ); - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'Focus should be on first item after sending button SPACE' - ); -}); + t.true( + await checkFocus(t, ex.menuitemSelector, 0), + 'Focus should be on first item after sending button SPACE' + ); + } +); -ariaTest('"up arrow" on menu button', exampleFile, 'button-up-arrow', async (t) => { - - await t.context.session - .findElement(By.css(ex.menubuttonSelector)) - .sendKeys(Key.ARROW_UP); +ariaTest( + '"up arrow" on menu button', + exampleFile, + 'button-up-arrow', + async (t) => { + await t.context.session + .findElement(By.css(ex.menubuttonSelector)) + .sendKeys(Key.ARROW_UP); - t.true( - await t.context.session.findElement(By.css(ex.menuitemSelector)).isDisplayed(), - 'The popup should be displayed after sending button ARROW_UP' - ); + t.true( + await t.context.session + .findElement(By.css(ex.menuitemSelector)) + .isDisplayed(), + 'The popup should be displayed after sending button ARROW_UP' + ); - t.true( - await checkFocus(t, ex.menuitemSelector, ex.numMenuitems - 1), - 'Focus should be on last item after sending button ARROW_UP' - ); -}); + t.true( + await checkFocus(t, ex.menuitemSelector, ex.numMenuitems - 1), + 'Focus should be on last item after sending button ARROW_UP' + ); + } +); ariaTest('"enter" on role="menuitem"', exampleFile, 'menu-enter', async (t) => { - for (let index = 0; index < ex.numMenuitems; index++) { - // Return to test page await t.context.session.get(t.context.url); const item = (await t.context.queryElements(t, ex.menuitemSelector))[index]; @@ -201,94 +279,110 @@ ariaTest('"enter" on role="menuitem"', exampleFile, 'menu-enter', async (t) => { t.not( await t.context.session.getCurrentUrl(), t.context.url, - 'Key enter when focus on list item at index ' + index + 'should active the link' + 'Key enter when focus on list item at index ' + + index + + 'should active the link' ); } }); -ariaTest('"escape" on role="menuitem"', exampleFile, 'menu-escape', async (t) => { - - const items = await t.context.queryElements(t, ex.menuitemSelector); - for (let index = 0; index < ex.numMenuitems; index++) { - const item = items[index]; +ariaTest( + '"escape" on role="menuitem"', + exampleFile, + 'menu-escape', + async (t) => { + const items = await t.context.queryElements(t, ex.menuitemSelector); + for (let index = 0; index < ex.numMenuitems; index++) { + const item = items[index]; + + await openMenu(t); + await item.sendKeys(Key.ESCAPE); + await waitForNoAriaExpanded(t); + + t.is( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Key escape when focus on list item at index ' + + index + + ' should not activate the link' + ); + + t.true( + await checkFocus(t, ex.menubuttonSelector, 0), + 'Key escape on item at index ' + + index + + ' should put focus back on menu.' + ); + } + } +); +ariaTest( + '"down arrow" on role="menuitem"', + exampleFile, + 'menu-down-arrow', + async (t) => { await openMenu(t); - await item.sendKeys(Key.ESCAPE); - await waitForNoAriaExpanded(t); - - t.is( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Key escape when focus on list item at index ' + index + ' should not activate the link' - ); - t.true( - await checkFocus(t, ex.menubuttonSelector, 0), - 'Key escape on item at index ' + index + ' should put focus back on menu.' - ); - } -}); + const items = await t.context.queryElements(t, ex.menuitemSelector); + for (let index = 0; index < items.length - 1; index++) { + await items[index].sendKeys(Key.ARROW_DOWN); -ariaTest('"down arrow" on role="menuitem"', exampleFile, 'menu-down-arrow', async (t) => { - - await openMenu(t); + const itemText = await items[index].getText(); + t.true( + await checkFocus(t, ex.menuitemSelector, index + 1), + 'down arrow on item "' + + itemText + + '" should put focus on the next item.' + ); + } - const items = await t.context.queryElements(t, ex.menuitemSelector); - for (let index = 0; index < items.length - 1; index++) { + await items[items.length - 1].sendKeys(Key.ARROW_DOWN); - await items[index].sendKeys(Key.ARROW_DOWN); - - const itemText = await items[index].getText(); + const itemText = await items[items.length - 1].getText(); t.true( - await checkFocus(t, ex.menuitemSelector, index + 1), - 'down arrow on item "' + itemText + '" should put focus on the next item.' + await checkFocus(t, ex.menuitemSelector, 0), + 'down arrow on item "' + itemText + '" should put focus to first item.' ); } +); - await items[items.length - 1].sendKeys(Key.ARROW_DOWN); - - const itemText = await items[items.length - 1].getText(); - t.true( - await checkFocus(t, ex.menuitemSelector, 0), - 'down arrow on item "' + itemText + '" should put focus to first item.' - ); - -}); - -ariaTest('"up arrow" on role="menuitem"', exampleFile, 'menu-up-arrow', async (t) => { - - await openMenu(t); - - const items = await t.context.queryElements(t, ex.menuitemSelector); - - await items[0].sendKeys(Key.ARROW_UP); - - const itemText = await items[0].getText(); - t.true( - await checkFocus(t, ex.menuitemSelector, items.length - 1), - 'up arrow on item "' + itemText + '" should put focus to last item.' - ); +ariaTest( + '"up arrow" on role="menuitem"', + exampleFile, + 'menu-up-arrow', + async (t) => { + await openMenu(t); - for (let index = items.length - 1; index > 0; index--) { + const items = await t.context.queryElements(t, ex.menuitemSelector); - await items[index].sendKeys(Key.ARROW_UP); + await items[0].sendKeys(Key.ARROW_UP); - const itemText = await items[index].getText(); + const itemText = await items[0].getText(); t.true( - await checkFocus(t, ex.menuitemSelector, index - 1), - 'down arrow on item "' + itemText + '" should put focus on the previous item.' + await checkFocus(t, ex.menuitemSelector, items.length - 1), + 'up arrow on item "' + itemText + '" should put focus to last item.' ); - } -}); + for (let index = items.length - 1; index > 0; index--) { + await items[index].sendKeys(Key.ARROW_UP); + + const itemText = await items[index].getText(); + t.true( + await checkFocus(t, ex.menuitemSelector, index - 1), + 'down arrow on item "' + + itemText + + '" should put focus on the previous item.' + ); + } + } +); ariaTest('"home" on role="menuitem"', exampleFile, 'menu-home', async (t) => { - await openMenu(t); const items = await t.context.queryElements(t, ex.menuitemSelector); for (let index = 0; index < items.length; index++) { - await items[index].sendKeys(Key.HOME); const itemText = await items[index].getText(); @@ -297,16 +391,13 @@ ariaTest('"home" on role="menuitem"', exampleFile, 'menu-home', async (t) => { 'key home on item "' + itemText + '" should put focus on the first time.' ); } - }); ariaTest('"end" on role="menuitem"', exampleFile, 'menu-end', async (t) => { - await openMenu(t); const items = await t.context.queryElements(t, ex.menuitemSelector); for (let index = 0; index < items.length; index++) { - await items[index].sendKeys(Key.END); const itemText = await items[index].getText(); @@ -317,25 +408,33 @@ ariaTest('"end" on role="menuitem"', exampleFile, 'menu-end', async (t) => { } }); -ariaTest('"character" on role="menuitem"', exampleFile, 'menu-character', async (t) => { - - const charIndexTest = [ - { sendChar: 'a', sendIndex: 0, endIndex: 2 }, - { sendChar: 'w', sendIndex: 2, endIndex: 3 }, - { sendChar: 'w', sendIndex: 3, endIndex: 4 }, - { sendChar: 'w', sendIndex: 4, endIndex: 0 } - ]; +ariaTest( + '"character" on role="menuitem"', + exampleFile, + 'menu-character', + async (t) => { + const charIndexTest = [ + { sendChar: 'a', sendIndex: 0, endIndex: 2 }, + { sendChar: 'w', sendIndex: 2, endIndex: 3 }, + { sendChar: 'w', sendIndex: 3, endIndex: 4 }, + { sendChar: 'w', sendIndex: 4, endIndex: 0 }, + ]; - await openMenu(t); - const items = await t.context.queryElements(t, ex.menuitemSelector); - - for (let test of charIndexTest) { - await items[test.sendIndex].sendKeys(test.sendChar); - - t.true( - await checkFocus(t, ex.menuitemSelector, test.endIndex), - 'Sending character "' + test.sendChar + '" to item at index ' + test.sendIndex + - '" should put focus on item at index: ' + test.endIndex - ); + await openMenu(t); + const items = await t.context.queryElements(t, ex.menuitemSelector); + + for (let test of charIndexTest) { + await items[test.sendIndex].sendKeys(test.sendChar); + + t.true( + await checkFocus(t, ex.menuitemSelector, test.endIndex), + 'Sending character "' + + test.sendChar + + '" to item at index ' + + test.sendIndex + + '" should put focus on item at index: ' + + test.endIndex + ); + } } -}); +); diff --git a/test/tests/menubar_menubar-editor.js b/test/tests/menubar_menubar-editor.js index 95d94a13c3..e736cf9ed8 100644 --- a/test/tests/menubar_menubar-editor.js +++ b/test/tests/menubar_menubar-editor.js @@ -24,190 +24,259 @@ const ex = { '#ex1 [role="menu"][aria-label="Font"] li[role="menuitemradio"]', '#ex1 [role="menu"][aria-label="Style/Color"] li[role="menuitemradio"],#ex1 [role="menu"][aria-label="Style/Color"] li[role="menuitemcheckbox"]', '#ex1 [role="menu"][aria-label="Text Align"] li[role="menuitemradio"]', - '#ex1 [role="menu"][aria-label="Size"] li[role="menuitem"],#ex1 [role="menu"][aria-label="Size"] li[role="menuitemradio"]' + '#ex1 [role="menu"][aria-label="Size"] li[role="menuitem"],#ex1 [role="menu"][aria-label="Size"] li[role="menuitemradio"]', ], radioItemGroupings: [ { menuIndex: 0, - itemsSelector: '#ex1 [role="menu"][aria-label="Font"] [role="menuitemradio"]', - defaultSelectedIndex: 0 + itemsSelector: + '#ex1 [role="menu"][aria-label="Font"] [role="menuitemradio"]', + defaultSelectedIndex: 0, }, { menuIndex: 1, - itemsSelector: '#ex1 [role="group"][aria-label="Text Color"] [role="menuitemradio"]', - defaultSelectedIndex: 0 + itemsSelector: + '#ex1 [role="group"][aria-label="Text Color"] [role="menuitemradio"]', + defaultSelectedIndex: 0, }, { menuIndex: 1, - itemsSelector: '#ex1 [role="group"][aria-label="Text Decoration"] [role="menuitemradio"]', - defaultSelectedIndex: 0 + itemsSelector: + '#ex1 [role="group"][aria-label="Text Decoration"] [role="menuitemradio"]', + defaultSelectedIndex: 0, }, { menuIndex: 2, - itemsSelector: '#ex1 [role="menu"][aria-label="Text Align"] [role="menuitemradio"]', - defaultSelectedIndex: 0 + itemsSelector: + '#ex1 [role="menu"][aria-label="Text Align"] [role="menuitemradio"]', + defaultSelectedIndex: 0, }, { menuIndex: 3, - itemsSelector: '#ex1 [role="group"][aria-label="Font Sizes"] [role="menuitemradio"]', - defaultSelectedIndex: 2 - } - ] + itemsSelector: + '#ex1 [role="group"][aria-label="Font Sizes"] [role="menuitemradio"]', + defaultSelectedIndex: 2, + }, + ], }; const exampleInitialized = async function (t) { const initializedSelector = ex.menubarMenuitemSelector + '[tabindex="0"]'; - await t.context.session.wait(async function () { - const els = await t.context.queryElements(t, initializedSelector); - return els.length === 1; - }, t.context.waitTime, 'Timeout waiting for example to initialize'); + await t.context.session.wait( + async function () { + const els = await t.context.queryElements(t, initializedSelector); + return els.length === 1; + }, + t.context.waitTime, + 'Timeout waiting for example to initialize' + ); }; const checkmarkVisible = async function (t, selector, index) { - return await t.context.session.executeScript(function () { - const [selector, index] = arguments; - const checkmarkContent = window.getComputedStyle( - document.querySelectorAll(selector)[index], ':before' - ).getPropertyValue('content'); - if (checkmarkContent == 'none') { - return false; - } - return true; - }, selector, index); + return await t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const checkmarkContent = window + .getComputedStyle(document.querySelectorAll(selector)[index], ':before') + .getPropertyValue('content'); + if (checkmarkContent == 'none') { + return false; + } + return true; + }, + selector, + index + ); }; const checkFocus = async function (t, selector, index) { - return await t.context.session.executeScript(function () { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return await t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; // Attributes -ariaTest('Test aria-label on textarea', exampleFile, 'textarea-aria-label', async (t) => { - t.plan(1); - await assertAriaLabelExists(t, ex.textareaSelector); -}); - -ariaTest('Test for role="menubar" on ul', exampleFile, 'menubar-role', async (t) => { - - - const menubars = await t.context.queryElements(t, ex.menubarSelector); - - t.is( - menubars.length, - 1, - 'One "role=menubar" element should be found by selector: ' + ex.menubarSelector - ); - - t.is( - await menubars[0].getTagName(), - 'ul', - '"role=menubar" should be found on a "ul"' - ); -}); - -ariaTest('Test aria-label on menubar', exampleFile, 'menubar-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.menubarSelector); -}); - -ariaTest('Test for role="menuitem" on li', exampleFile, 'menubar-menuitem-role', async (t) => { - +ariaTest( + 'Test aria-label on textarea', + exampleFile, + 'textarea-aria-label', + async (t) => { + t.plan(1); + await assertAriaLabelExists(t, ex.textareaSelector); + } +); - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); +ariaTest( + 'Test for role="menubar" on ul', + exampleFile, + 'menubar-role', + async (t) => { + const menubars = await t.context.queryElements(t, ex.menubarSelector); - t.is( - menuitems.length, - 4, - '"role=menuitem" elements should be found by selector: ' + ex.menubarMenuitemSelector - ); + t.is( + menubars.length, + 1, + 'One "role=menubar" element should be found by selector: ' + + ex.menubarSelector + ); - for (let menuitem of menuitems) { - t.truthy( - await menuitem.getText(), - '"role=menuitem" elements should all have accessible text content: ' + ex.menubarMenuitemSelector + t.is( + await menubars[0].getTagName(), + 'ul', + '"role=menubar" should be found on a "ul"' ); } -}); - -ariaTest('Test roving tabindex', exampleFile, 'menubar-menuitem-tabindex', async (t) => { - +); - // Wait for roving tabindex to be initialized by the javascript - await exampleInitialized(t); - - await assertRovingTabindex(t, ex.menubarMenuitemSelector, Key.ARROW_RIGHT); -}); - -ariaTest('Test aria-haspopup set to true on menuitems', - exampleFile, 'menubar-menuitem-aria-haspopup', async (t) => { - - - await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-haspopup', 'true'); - }); +ariaTest( + 'Test aria-label on menubar', + exampleFile, + 'menubar-aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.menubarSelector); + } +); + +ariaTest( + 'Test for role="menuitem" on li', + exampleFile, + 'menubar-menuitem-role', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); -ariaTest('"aria-expanded" attribute on menubar menuitem', exampleFile, 'menubar-menuitem-aria-expanded', async (t) => { + t.is( + menuitems.length, + 4, + '"role=menuitem" elements should be found by selector: ' + + ex.menubarMenuitemSelector + ); + for (let menuitem of menuitems) { + t.truthy( + await menuitem.getText(), + '"role=menuitem" elements should all have accessible text content: ' + + ex.menubarMenuitemSelector + ); + } + } +); - // Before interating with page, make sure aria-expanded is set to false - await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-expanded', 'false'); +ariaTest( + 'Test roving tabindex', + exampleFile, + 'menubar-menuitem-tabindex', + async (t) => { + // Wait for roving tabindex to be initialized by the javascript + await exampleInitialized(t); - // AND make sure no submenus are visible - const submenus = await t.context.queryElements(t, ex.submenuSelector); - for (let submenu of submenus) { - t.false( - await submenu.isDisplayed(), - 'No submenus (found by selector: ' + ex.submenuSelector + ') should be displayed on load' + await assertRovingTabindex(t, ex.menubarMenuitemSelector, Key.ARROW_RIGHT); + } +); + +ariaTest( + 'Test aria-haspopup set to true on menuitems', + exampleFile, + 'menubar-menuitem-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.menubarMenuitemSelector, + 'aria-haspopup', + 'true' ); } +); + +ariaTest( + '"aria-expanded" attribute on menubar menuitem', + exampleFile, + 'menubar-menuitem-aria-expanded', + async (t) => { + // Before interating with page, make sure aria-expanded is set to false + await assertAttributeValues( + t, + ex.menubarMenuitemSelector, + 'aria-expanded', + 'false' + ); - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + // AND make sure no submenus are visible + const submenus = await t.context.queryElements(t, ex.submenuSelector); + for (let submenu of submenus) { + t.false( + await submenu.isDisplayed(), + 'No submenus (found by selector: ' + + ex.submenuSelector + + ') should be displayed on load' + ); + } - for (let menuIndex = 0; menuIndex < menuitems.length; menuIndex++) { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Send ARROW_DOWN to open submenu - await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + for (let menuIndex = 0; menuIndex < menuitems.length; menuIndex++) { + // Send ARROW_DOWN to open submenu + await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - for (let item = 0; item < menuitems.length; item++) { + for (let item = 0; item < menuitems.length; item++) { + // Test attribute "aria-expanded" is only set for the opened submenu + const displayed = menuIndex === item ? true : false; + t.is( + await menuitems[item].getAttribute('aria-expanded'), + displayed.toString(), + 'focus is on element ' + + menuIndex + + ' of elements "' + + ex.menubarMenuitemSelector + + '", therefore "aria-expanded" on menuitem ' + + item + + ' should be ' + + displayed + ); - // Test attribute "aria-expanded" is only set for the opened submenu - const displayed = menuIndex === item ? true : false; - t.is( - await menuitems[item].getAttribute('aria-expanded'), - displayed.toString(), - 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + - '", therefore "aria-expanded" on menuitem ' + item + ' should be ' + displayed - ); + // Test the submenu is indeed displayed + t.is( + await submenus[item].isDisplayed(), + displayed, + 'focus is on element ' + + menuIndex + + ' of elements "' + + ex.menubarMenuitemSelector + + '", therefore isDisplay of submenu ' + + item + + ' should return ' + + displayed + ); + } - // Test the submenu is indeed displayed - t.is( - await submenus[item].isDisplayed(), - displayed, - 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + - '", therefore isDisplay of submenu ' + item + ' should return ' + displayed - ); + // Send the ESCAPE to close submenu + await menuitems[menuIndex].sendKeys(Key.ESCAPE); } - - // Send the ESCAPE to close submenu - await menuitems[menuIndex].sendKeys(Key.ESCAPE); } - -}); - +); ariaTest('Test for role="menu" on ul', exampleFile, 'menu-role', async (t) => { - - const submenus = await t.context.queryElements(t, ex.submenuSelector); // Test elements with role="menu" exist t.is( submenus.length, 4, - 'Four role="menu" elements should be found by selector: ' + ex.submenuSelector + 'Four role="menu" elements should be found by selector: ' + + ex.submenuSelector ); // Test the role="menu" elements are all on "ul" items @@ -222,64 +291,99 @@ ariaTest('Test for role="menu" on ul', exampleFile, 'menu-role', async (t) => { await assertAttributeValues(t, ex.submenuSelector, 'tabindex', '-1'); }); -ariaTest('Test for aria-label on role="menu"', exampleFile, 'menu-aria-label', async (t) => { - - const submenusSelectors = [ - '#ex1 li:nth-of-type(1) [role="menu"]', - '#ex1 li:nth-of-type(2) [role="menu"]', - '#ex1 li:nth-of-type(3) [role="menu"]', - '#ex1 li:nth-of-type(4) [role="menu"]' - ]; +ariaTest( + 'Test for aria-label on role="menu"', + exampleFile, + 'menu-aria-label', + async (t) => { + const submenusSelectors = [ + '#ex1 li:nth-of-type(1) [role="menu"]', + '#ex1 li:nth-of-type(2) [role="menu"]', + '#ex1 li:nth-of-type(3) [role="menu"]', + '#ex1 li:nth-of-type(4) [role="menu"]', + ]; - for (let submenuSelector of submenusSelectors) { - await assertAriaLabelExists(t, submenuSelector); + for (let submenuSelector of submenusSelectors) { + await assertAriaLabelExists(t, submenuSelector); + } } -}); - -ariaTest('Test for submenu menuitems with accessible names', exampleFile, 'submenu-menuitem-role', async (t) => { - - - const menuitems = await t.context.queryElements(t, ex.submenuMenuitemSelector); - - t.truthy( - menuitems.length, - '"role=menuitem" elements should be found by selector: ' + ex.submenuMenuitemSelector - ); - - // Test the accessible name of each menuitem - - for (let menuitem of menuitems) { - - // The menuitem is not visible, so we cannot use selenium's "getText" function - const menutext = await t.context.session.executeScript(function () { - const el = arguments[0]; - return el.innerHTML; - }, menuitem); +); + +ariaTest( + 'Test for submenu menuitems with accessible names', + exampleFile, + 'submenu-menuitem-role', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.submenuMenuitemSelector + ); t.truthy( - menutext, - '"role=menuitem" elements should all have accessible text content: ' + ex.submenuMenuitemSelector + menuitems.length, + '"role=menuitem" elements should be found by selector: ' + + ex.submenuMenuitemSelector ); - } - -}); -ariaTest('Test tabindex="-1" for all submenu role="menuitem"s', - exampleFile, 'submenu-menuitem-tabindex', async (t) => { + // Test the accessible name of each menuitem - await assertAttributeValues(t, ex.submenuMenuitemSelector, 'tabindex', '-1'); - }); + for (let menuitem of menuitems) { + // The menuitem is not visible, so we cannot use selenium's "getText" function + const menutext = await t.context.session.executeScript(function () { + const el = arguments[0]; + return el.innerHTML; + }, menuitem); -ariaTest('Test aria-disabled="false" for all submenu role="menuitem"s', - exampleFile, 'submenu-menuitem-aria-disabled', async (t) => { + t.truthy( + menutext, + '"role=menuitem" elements should all have accessible text content: ' + + ex.submenuMenuitemSelector + ); + } + } +); + +ariaTest( + 'Test tabindex="-1" for all submenu role="menuitem"s', + exampleFile, + 'submenu-menuitem-tabindex', + async (t) => { + await assertAttributeValues( + t, + ex.submenuMenuitemSelector, + 'tabindex', + '-1' + ); + } +); +ariaTest( + 'Test aria-disabled="false" for all submenu role="menuitem"s', + exampleFile, + 'submenu-menuitem-aria-disabled', + async (t) => { // "aria-disable" should be set to false by default - await assertAttributeValues(t, ex.submenuMenuitemSelector, 'aria-disabled', 'false'); + await assertAttributeValues( + t, + ex.submenuMenuitemSelector, + 'aria-disabled', + 'false' + ); const menus = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const sizeMenu = await t.context.session.findElement(By.css('[aria-label="Size"]')); - const menuitems = await t.context.queryElements(t, '[role="menuitem"]', sizeMenu); - const menuItemRadios = await t.context.queryElements(t, '[role="menuitemradio"]', sizeMenu); + const sizeMenu = await t.context.session.findElement( + By.css('[aria-label="Size"]') + ); + const menuitems = await t.context.queryElements( + t, + '[role="menuitem"]', + sizeMenu + ); + const menuItemRadios = await t.context.queryElements( + t, + '[role="menuitemradio"]', + sizeMenu + ); // select X-Small size await menus[3].sendKeys(Key.ARROW_DOWN); @@ -304,54 +408,82 @@ ariaTest('Test aria-disabled="false" for all submenu role="menuitem"s', disabledSecondItem === 'true', 'The second menuitem in the last dropdown should become disabled after X-Large is selected' ); + } +); + +ariaTest( + 'Test for role="menuitemcheckbox" on li', + exampleFile, + 'menuitemcheckbox-role', + async (t) => { + const checkboxes = await t.context.queryElements( + t, + ex.menuitemcheckboxSelector + ); + t.truthy( + checkboxes.length, + '"role=menuitemcheckbox" elements should be found by selector: ' + + ex.menuitemcheckboxSelector + ); - }); - -ariaTest('Test for role="menuitemcheckbox" on li', exampleFile, 'menuitemcheckbox-role', async (t) => { - - const checkboxes = await t.context.queryElements(t, ex.menuitemcheckboxSelector); - - t.truthy( - checkboxes.length, - '"role=menuitemcheckbox" elements should be found by selector: ' + ex.menuitemcheckboxSelector - ); - - // Test the accessible name of each menuitem - - for (let checkbox of checkboxes) { + // Test the accessible name of each menuitem - // The menuitem is not visible, so we cannot use selenium's "getText" function - const text = await t.context.session.executeScript(function () { - const el = arguments[0]; - return el.innerHTML; - }, checkbox); + for (let checkbox of checkboxes) { + // The menuitem is not visible, so we cannot use selenium's "getText" function + const text = await t.context.session.executeScript(function () { + const el = arguments[0]; + return el.innerHTML; + }, checkbox); - t.truthy( - text, - '"role=menuitemcheckbox" elements should all have accessible text content: ' + ex.menuitemcheckboxSelector + t.truthy( + text, + '"role=menuitemcheckbox" elements should all have accessible text content: ' + + ex.menuitemcheckboxSelector + ); + } + } +); + +ariaTest( + 'Test tabindex="-1" for role="menuitemcheckbox"', + exampleFile, + 'menuitemcheckbox-tabindex', + async (t) => { + await assertAttributeValues( + t, + ex.menuitemcheckboxSelector, + 'tabindex', + '-1' ); } -}); - -ariaTest('Test tabindex="-1" for role="menuitemcheckbox"', exampleFile, 'menuitemcheckbox-tabindex', async (t) => { - await assertAttributeValues(t, ex.menuitemcheckboxSelector, 'tabindex', '-1'); -}); - -ariaTest('Test "aria-checked" attirbute on role="menuitemcheckbox"', - exampleFile, 'menuitemcheckbox-aria-checked', async (t) => { +); +ariaTest( + 'Test "aria-checked" attirbute on role="menuitemcheckbox"', + exampleFile, + 'menuitemcheckbox-aria-checked', + async (t) => { const menus = await t.context.queryElements(t, ex.menubarMenuitemSelector); // Reveal the menuitemcheckbox elements in the second dropdown await menus[1].sendKeys(Key.ARROW_DOWN); // Confirm aria-checked is set to false by default - await assertAttributeValues(t, ex.menuitemcheckboxSelector, 'aria-checked', 'false'); + await assertAttributeValues( + t, + ex.menuitemcheckboxSelector, + 'aria-checked', + 'false' + ); // And corrospondingly, neither item should have a visible checkmark for (let checkIndex = 0; checkIndex < 2; checkIndex++) { - const checkmark = await checkmarkVisible(t, ex.menuitemcheckboxSelector, checkIndex); + const checkmark = await checkmarkVisible( + t, + ex.menuitemcheckboxSelector, + checkIndex + ); t.false( checkmark, 'All menuitemcheckbox items should not have checkmark prepended' @@ -359,54 +491,77 @@ ariaTest('Test "aria-checked" attirbute on role="menuitemcheckbox"', } // Select both menuitems - const checkboxes = await t.context.queryElements(t, ex.menuitemcheckboxSelector); + const checkboxes = await t.context.queryElements( + t, + ex.menuitemcheckboxSelector + ); await checkboxes[0].sendKeys(Key.ENTER); await menus[1].sendKeys(Key.ARROW_DOWN); await checkboxes[1].sendKeys(Key.ENTER); await menus[1].sendKeys(Key.ARROW_DOWN); - // Confirm aria-checked is set to true - await assertAttributeValues(t, ex.menuitemcheckboxSelector, 'aria-checked', 'true'); + await assertAttributeValues( + t, + ex.menuitemcheckboxSelector, + 'aria-checked', + 'true' + ); // And corrospondingly, both items should have a visible checkmark for (let checkIndex = 0; checkIndex < 2; checkIndex++) { - const checkmark = await checkmarkVisible(t, ex.menuitemcheckboxSelector, checkIndex); + const checkmark = await checkmarkVisible( + t, + ex.menuitemcheckboxSelector, + checkIndex + ); t.true( checkmark, 'All menuitemcheckbox items should have checkmark prepended' ); } - }); - + } +); -ariaTest('Test role="separator" exists', exampleFile, 'separator-role', async (t) => { +ariaTest( + 'Test role="separator" exists', + exampleFile, + 'separator-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'separator', 3, 'li'); -}); + } +); ariaTest('Test role="group" exists', exampleFile, 'group-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'group', 4, 'ul'); + await assertAriaRoles(t, 'ex1', 'group', 4, 'ul'); }); -ariaTest('Test aria-label on group', exampleFile, 'group-aria-label', async (t) => { +ariaTest( + 'Test aria-label on group', + exampleFile, + 'group-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.groupSelector); -}); - -ariaTest('Test role="menuitemradio" exists with accessible name', - exampleFile, 'menuitemradio-role', async (t) => { + } +); +ariaTest( + 'Test role="menuitemradio" exists with accessible name', + exampleFile, + 'menuitemradio-role', + async (t) => { const items = await t.context.queryElements(t, ex.menuitemradioSelector); // Test that the elements exist t.truthy( items.length, - '"role=menuitemradio" elements should be found by selector: ' + ex.menuitemradioSelector + '"role=menuitemradio" elements should be found by selector: ' + + ex.menuitemradioSelector ); // Test for accessible name for (let item of items) { - // The menuitem is not visible, so we cannot use selenium's "getText" function const text = await t.context.session.executeScript(function () { const el = arguments[0]; @@ -415,23 +570,30 @@ ariaTest('Test role="menuitemradio" exists with accessible name', t.truthy( text, - '"role=menuitemradio" elements should all have accessible text content: ' + ex.menuitemradio + '"role=menuitemradio" elements should all have accessible text content: ' + + ex.menuitemradio ); } - }); - -ariaTest('Test tabindex="-1" on role="menuitemradio"', - exampleFile, 'menuitemradio-tabindex', async (t) => { - await assertAttributeValues(t, ex.menuitemradioSelector, 'tabindex', '-1'); - }); - -ariaTest('Text "aria-checked" appropriately set on role="menitemradio"', - exampleFile, 'menuitemradio-aria-checked', async (t) => { + } +); + +ariaTest( + 'Test tabindex="-1" on role="menuitemradio"', + exampleFile, + 'menuitemradio-tabindex', + async (t) => { + await assertAttributeValues(t, ex.menuitemradioSelector, 'tabindex', '-1'); + } +); +ariaTest( + 'Text "aria-checked" appropriately set on role="menitemradio"', + exampleFile, + 'menuitemradio-aria-checked', + async (t) => { const menus = await t.context.queryElements(t, ex.menubarMenuitemSelector); for (let grouping of ex.radioItemGroupings) { - // Reveal the elements in the dropdown await menus[grouping.menuIndex].sendKeys(Key.ARROW_DOWN); @@ -440,100 +602,138 @@ ariaTest('Text "aria-checked" appropriately set on role="menitemradio"', // Test for the initial state of checked/not checked for all radio menuitems for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { - - const selected = itemIndex === grouping.defaultSelectedIndex ? true : false; + const selected = + itemIndex === grouping.defaultSelectedIndex ? true : false; t.is( await items[itemIndex].getAttribute('aria-checked'), selected.toString(), - 'Only item ' + grouping.defaultSelectedIndex + ' should have aria-select="true" in menu dropdown items: ' + grouping.itemsSelector + 'Only item ' + + grouping.defaultSelectedIndex + + ' should have aria-select="true" in menu dropdown items: ' + + grouping.itemsSelector ); - const checkmark = await checkmarkVisible(t, grouping.itemsSelector, itemIndex); + const checkmark = await checkmarkVisible( + t, + grouping.itemsSelector, + itemIndex + ); t.is( checkmark, selected, - 'Only item ' + grouping.defaultSelectedIndex + ' should be selected in menu dropdown items: ' + grouping.itemsSelector + 'Only item ' + + grouping.defaultSelectedIndex + + ' should be selected in menu dropdown items: ' + + grouping.itemsSelector ); } } - }); + } +); // KEYS -ariaTest('Key ENTER open submenu', exampleFile, 'menubar-key-space-and-enter', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const submenus = await t.context.queryElements(t, ex.submenuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ENTER key - await menuitems[menuIndex].sendKeys(Key.ENTER); - - // Test that the submenu is displayed - t.true( - await submenus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' +ariaTest( + 'Key ENTER open submenu', + exampleFile, + 'menubar-key-space-and-enter', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector ); + const submenus = await t.context.queryElements(t, ex.submenuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the ENTER key + await menuitems[menuIndex].sendKeys(Key.ENTER); - t.true( - await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); - } -}); - -ariaTest('Key SPACE open submenu', exampleFile, 'menubar-key-space-and-enter', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const submenus = await t.context.queryElements(t, ex.submenuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the SPACE key - await menuitems[menuIndex].sendKeys(' '); + // Test that the submenu is displayed + t.true( + await submenus[menuIndex].isDisplayed(), + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should display submenu' + ); - // Test that the submenu is displayed - t.true( - await submenus[menuIndex].isDisplayed(), - 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should display submenu' + t.true( + await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' + ); + } + } +); + +ariaTest( + 'Key SPACE open submenu', + exampleFile, + 'menubar-key-space-and-enter', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector ); + const submenus = await t.context.queryElements(t, ex.submenuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the SPACE key + await menuitems[menuIndex].sendKeys(' '); - t.true( - await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), - 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); + // Test that the submenu is displayed + t.true( + await submenus[menuIndex].isDisplayed(), + 'Sending key "SPACE" to menuitem ' + + menuIndex + + ' in menubar should display submenu' + ); + t.true( + await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), + 'Sending key "SPACE" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' + ); + } } -}); - - -ariaTest('Key ESCAPE closes menubar', exampleFile, 'menubar-key-escape', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const submenus = await t.context.queryElements(t, ex.submenuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ENTER key, then the ESCAPE - await menuitems[menuIndex].sendKeys(Key.ENTER, Key.ESCAPE); - - // Test that the submenu is not displayed - t.false( - await submenus[menuIndex].isDisplayed(), - 'Sending key "ESCAPE" to menuitem ' + menuIndex + ' in menubar should close the open submenu' +); + +ariaTest( + 'Key ESCAPE closes menubar', + exampleFile, + 'menubar-key-escape', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector ); - } - -}); - - -ariaTest('Key ARROW_RIGHT moves focus to next menubar item', - exampleFile, 'menubar-key-right-arrow', async (t) => { - + const submenus = await t.context.queryElements(t, ex.submenuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the ENTER key, then the ESCAPE + await menuitems[menuIndex].sendKeys(Key.ENTER, Key.ESCAPE); - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + // Test that the submenu is not displayed + t.false( + await submenus[menuIndex].isDisplayed(), + 'Sending key "ESCAPE" to menuitem ' + + menuIndex + + ' in menubar should close the open submenu' + ); + } + } +); + +ariaTest( + 'Key ARROW_RIGHT moves focus to next menubar item', + exampleFile, + 'menubar-key-right-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); for (let menuIndex = 0; menuIndex < ex.numMenus + 1; menuIndex++) { - const currentIndex = menuIndex % ex.numMenus; const nextIndex = (menuIndex + 1) % ex.numMenus; @@ -543,16 +743,24 @@ ariaTest('Key ARROW_RIGHT moves focus to next menubar item', // Test the focus is on the next item mod the number of items to account for wrapping t.true( await checkFocus(t, ex.menubarMenuitemSelector, nextIndex), - 'Sending key "ARROW_RIGHT" to menuitem ' + currentIndex + ' should move focus to menuitem ' + nextIndex + 'Sending key "ARROW_RIGHT" to menuitem ' + + currentIndex + + ' should move focus to menuitem ' + + nextIndex ); } - }); - -ariaTest('Key ARROW_RIGHT moves focus to next menubar item', - exampleFile, 'menubar-key-left-arrow', async (t) => { - - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + } +); + +ariaTest( + 'Key ARROW_RIGHT moves focus to next menubar item', + exampleFile, + 'menubar-key-left-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); // Send the ARROW_LEFT key to the first menuitem await menuitems[0].sendKeys(Key.ARROW_LEFT); @@ -563,117 +771,155 @@ ariaTest('Key ARROW_RIGHT moves focus to next menubar item', 'Sending key "ARROW_LEFT" to menuitem 0 will change focus to menu item 3' ); - for (let menuIndex = ex.numMenus - 1; menuIndex > 0; menuIndex--) { - // Send the ARROW_LEFT key await menuitems[menuIndex].sendKeys(Key.ARROW_LEFT); // Test the focus is on the previous menuitem t.true( await checkFocus(t, ex.menubarMenuitemSelector, menuIndex - 1), - 'Sending key "ARROW_RIGHT" to menuitem ' + menuIndex + ' should move focus to menuitem ' + (menuIndex - 1) + 'Sending key "ARROW_RIGHT" to menuitem ' + + menuIndex + + ' should move focus to menuitem ' + + (menuIndex - 1) ); } - }); - -ariaTest('Key ARROW_UP opens submenu, focus on last item', - exampleFile, 'menubar-key-up-arrow', async (t) => { - - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + } +); + +ariaTest( + 'Key ARROW_UP opens submenu, focus on last item', + exampleFile, + 'menubar-key-up-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const submenus = await t.context.queryElements(t, ex.submenuSelector); for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - // Send the ENTER key await menuitems[menuIndex].sendKeys(Key.UP); // Test that the submenu is displayed t.true( await submenus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should display submenu' ); - const numSubItems = (await t.context.queryElements(t, ex.allSubmenuItems[menuIndex])).length; + const numSubItems = ( + await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]) + ).length; // Test that the focus is on the last item in the list t.true( await checkFocus(t, ex.allSubmenuItems[menuIndex], numSubItems - 1), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' ); } - }); - -ariaTest('Key ARROW_DOWN opens submenu, focus on first item', - exampleFile, 'menubar-key-down-arrow', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + } +); + +ariaTest( + 'Key ARROW_DOWN opens submenu, focus on first item', + exampleFile, + 'menubar-key-down-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const submenus = await t.context.queryElements(t, ex.submenuSelector); for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - // Send the ENTER key await menuitems[menuIndex].sendKeys(Key.DOWN); // Test that the submenu is displayed t.true( await submenus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should display submenu' ); // Test that the focus is on the first item in the list t.true( - await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' ); } - }); - -ariaTest('Key HOME goes to first item in menubar', exampleFile, 'menubar-key-home', async (t) => { + } +); + +ariaTest( + 'Key HOME goes to first item in menubar', + exampleFile, + 'menubar-key-home', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the ARROW_RIGHT key to move the focus to later menu item for every test + for (let i = 0; i < menuIndex; i++) { + await menuitems[i].sendKeys(Key.ARROW_RIGHT); + } - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the key HOME + await menuitems[menuIndex].sendKeys(Key.HOME); - // Send the ARROW_RIGHT key to move the focus to later menu item for every test - for (let i = 0; i < menuIndex; i++) { - await menuitems[i].sendKeys(Key.ARROW_RIGHT); + // Test that the focus is on the first item in the list + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, 0), + 'Sending key "HOME" to menuitem ' + + menuIndex + + ' in menubar should move the foucs to the first menuitem' + ); } - - // Send the key HOME - await menuitems[menuIndex].sendKeys(Key.HOME); - - // Test that the focus is on the first item in the list - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, 0), - 'Sending key "HOME" to menuitem ' + menuIndex + ' in menubar should move the foucs to the first menuitem' - ); } -}); - -ariaTest('Key END goes to last item in menubar', exampleFile, 'menubar-key-end', async (t) => { +); + +ariaTest( + 'Key END goes to last item in menubar', + exampleFile, + 'menubar-key-end', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the ARROW_RIGHT key to move the focus to later menu item for every test + for (let i = 0; i < menuIndex; i++) { + await menuitems[i].sendKeys(Key.ARROW_RIGHT); + } - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the key END + await menuitems[menuIndex].sendKeys(Key.END); - // Send the ARROW_RIGHT key to move the focus to later menu item for every test - for (let i = 0; i < menuIndex; i++) { - await menuitems[i].sendKeys(Key.ARROW_RIGHT); + // Test that the focus is on the last item in the list + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), + 'Sending key "END" to menuitem ' + + menuIndex + + ' in menubar should move the foucs to the last menuitem' + ); } - - // Send the key END - await menuitems[menuIndex].sendKeys(Key.END); - - // Test that the focus is on the last item in the list - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), - 'Sending key "END" to menuitem ' + menuIndex + ' in menubar should move the foucs to the last menuitem' - ); } -}); - -ariaTest('Character sends to menubar changes focus in menubar', - exampleFile, 'menubar-key-character', async (t) => { - +); +ariaTest( + 'Character sends to menubar changes focus in menubar', + exampleFile, + 'menubar-key-character', + async (t) => { const charIndexTest = [ { sendChar: 'f', sendIndex: 0, endIndex: 0 }, { sendChar: 's', sendIndex: 0, endIndex: 1 }, @@ -681,129 +927,182 @@ ariaTest('Character sends to menubar changes focus in menubar', { sendChar: 'f', sendIndex: 1, endIndex: 0 }, { sendChar: 's', sendIndex: 1, endIndex: 3 }, { sendChar: 'z', sendIndex: 0, endIndex: 0 }, - { sendChar: 'z', sendIndex: 3, endIndex: 3 } + { sendChar: 'z', sendIndex: 3, endIndex: 3 }, ]; - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); for (let test of charIndexTest) { - // Send character to menuitem await menuitems[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate menuitem t.true( - await checkFocus(t, ex.menubarMenuitemSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to menuitem ' + test.sendIndex + ' in menubar should move the focus to menuitem ' + test.endIndex + await checkFocus(t, ex.menubarMenuitemSelector, test.endIndex), + 'Sending character ' + + test.sendChar + + ' to menuitem ' + + test.sendIndex + + ' in menubar should move the focus to menuitem ' + + test.endIndex ); } - }); - -ariaTest('ENTER in submenu selects item', exampleFile, 'submenu-enter', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const submenus = await t.context.queryElements(t, ex.submenuSelector); + } +); + +ariaTest( + 'ENTER in submenu selects item', + exampleFile, + 'submenu-enter', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const submenus = await t.context.queryElements(t, ex.submenuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { + // Open the submenu + await menuitems[menuIndex].sendKeys(Key.ENTER); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); + const item = items[itemIndex]; + const itemText = await item.getText(); - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { + // Get the current style attribute on the "Text Sample" + const originalStyle = await t.context.session + .findElement(By.css(ex.textareaSelector)) + .getAttribute('style'); - // Open the submenu - await menuitems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); - const item = items[itemIndex]; - const itemText = await item.getText(); + // send ENTER to the item + await item.sendKeys(Key.ENTER); - // Get the current style attribute on the "Text Sample" - const originalStyle = await t.context.session - .findElement(By.css(ex.textareaSelector)) - .getAttribute('style'); + // Test that the submenu is closed + t.false( + await submenus[menuIndex].isDisplayed(), + 'Sending key "ENTER" to submenuitem "' + + itemText + + '" should close list' + ); - // send ENTER to the item - await item.sendKeys(Key.ENTER); + // Test that the focus is back on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), + 'Sending key "ENTER" to submenuitem "' + + itemText + + '" should change the focus to menuitem ' + + menuIndex + + ' in the menubar' + ); - // Test that the submenu is closed - t.false( - await submenus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to submenuitem "' + itemText + '" should close list' - ); + let changedStyle = true; + if (itemIndex === 0 && menuIndex === 0) { + // Only when selecting the first (selected by default) font option will the style not change. + changedStyle = false; + } - // Test that the focus is back on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), - 'Sending key "ENTER" to submenuitem "' + itemText + '" should change the focus to menuitem ' + menuIndex + ' in the menubar' - ); + // Get the current style attribute on the "Text Sample" + const currentStyle = await t.context.session + .findElement(By.css(ex.textareaSelector)) + .getAttribute('style'); - let changedStyle = true; - if (itemIndex === 0 && menuIndex === 0) { - // Only when selecting the first (selected by default) font option will the style not change. - changedStyle = false; + t.is( + currentStyle != originalStyle, + changedStyle, + 'Sending key "ENTER" to submenuitem "' + + itemText + + '" should change the style attribute on the Text Sampe.' + ); } - - // Get the current style attribute on the "Text Sample" - const currentStyle = await t.context.session - .findElement(By.css(ex.textareaSelector)) - .getAttribute('style'); - - t.is( - currentStyle != originalStyle, - changedStyle, - 'Sending key "ENTER" to submenuitem "' + itemText + '" should change the style attribute on the Text Sampe.' - ); } } +); + +ariaTest( + 'ESCAPE to submenu closes submenu', + exampleFile, + 'submenu-escape', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const submenus = await t.context.queryElements(t, ex.submenuSelector); -}); - -ariaTest('ESCAPE to submenu closes submenu', exampleFile, 'submenu-escape', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const submenus = await t.context.queryElements(t, ex.submenuSelector); - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { - - // Open the submenu - await menuitems[menuIndex].sendKeys(Key.ENTER); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { + // Open the submenu + await menuitems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); - const item = items[itemIndex]; - const itemText = await item.getText(); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); + const item = items[itemIndex]; + const itemText = await item.getText(); - // send escape to the item - await item.sendKeys(Key.ESCAPE); + // send escape to the item + await item.sendKeys(Key.ESCAPE); - // make sure focus is on the menuitem and the popup is submenu is closed - t.false( - await submenus[menuIndex].isDisplayed(), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should close list' - ); + // make sure focus is on the menuitem and the popup is submenu is closed + t.false( + await submenus[menuIndex].isDisplayed(), + 'Sending key "ESCAPE" to submenuitem "' + + itemText + + '" should close list' + ); - // Test that the focus is back on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should change the focus to menuitem ' + menuIndex + ' in the menubar' - ); + // Test that the focus is back on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), + 'Sending key "ESCAPE" to submenuitem "' + + itemText + + '" should change the focus to menuitem ' + + menuIndex + + ' in the menubar' + ); + } } } - -}); - -ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', - exampleFile, 'submenu-right-arrow', async (t) => { - - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); +); + +ariaTest( + 'ARROW_RIGHT to submenu closes submenu and opens next', + exampleFile, + 'submenu-right-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const submenus = await t.context.queryElements(t, ex.submenuSelector); for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { - + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { // Open the submenu await menuitems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); const item = items[itemIndex]; const itemText = await item.getText(); const nextMenuIndex = (menuIndex + 1) % ex.numMenus; @@ -814,38 +1113,58 @@ ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', // Test that the submenu is closed t.false( await submenus[menuIndex].isDisplayed(), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should close list' + 'Sending key "ARROW_RIGHT" to submenuitem "' + + itemText + + '" should close list' ); // Test that the next submenu is open t.true( await submenus[nextMenuIndex].isDisplayed(), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should open submenu ' + nextMenuIndex + 'Sending key "ARROW_RIGHT" to submenuitem "' + + itemText + + '" should open submenu ' + + nextMenuIndex ); // Test that the focus is on the menuitem in the menubar t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_RIGHT" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextMenuIndex + + ' in the menubar' ); } } - }); - -ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', - exampleFile, 'submenu-left-arrow', async (t) => { - - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + } +); + +ariaTest( + 'ARROW_RIGHT to submenu closes submenu and opens next', + exampleFile, + 'submenu-left-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const submenus = await t.context.queryElements(t, ex.submenuSelector); for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { - + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { // Open the submenu await menuitems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); const item = items[itemIndex]; const itemText = await item.getText(); // Account for wrapping (index 0 should go to 3) @@ -857,167 +1176,249 @@ ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', // Test that the submenu is closed t.false( await submenus[menuIndex].isDisplayed(), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should close list' + 'Sending key "ARROW_LEFT" to submenuitem "' + + itemText + + '" should close list' ); // Test that the next submenu is open t.true( await submenus[nextMenuIndex].isDisplayed(), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should open submenu ' + nextMenuIndex + 'Sending key "ARROW_LEFT" to submenuitem "' + + itemText + + '" should open submenu ' + + nextMenuIndex ); // Test that the focus is on the menuitem in the menubar t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_LEFT" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextMenuIndex + + ' in the menubar' ); } } - }); - -ariaTest('ARROW_DOWN moves focus to next item', exampleFile, 'submenu-down-arrow', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + } +); + +ariaTest( + 'ARROW_DOWN moves focus to next item', + exampleFile, + 'submenu-down-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Open the submenu - await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Open the submenu + await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { - const item = items[itemIndex]; - const itemText = await item.getText(); - const nextItemIndex = (itemIndex + 1) % ex.numSubmenuItems[menuIndex]; + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { + const item = items[itemIndex]; + const itemText = await item.getText(); + const nextItemIndex = (itemIndex + 1) % ex.numSubmenuItems[menuIndex]; - // send DOWN to the item - await item.sendKeys(Key.ARROW_DOWN); + // send DOWN to the item + await item.sendKeys(Key.ARROW_DOWN); - // Test that the focus is on the next item - t.true( - await checkFocus(t, ex.allSubmenuItems[menuIndex], nextItemIndex), - 'Sending key "ARROW_DOWN" to submenu item "' + itemText + '" should send focus to next submenu item.' - ); + // Test that the focus is on the next item + t.true( + await checkFocus(t, ex.allSubmenuItems[menuIndex], nextItemIndex), + 'Sending key "ARROW_DOWN" to submenu item "' + + itemText + + '" should send focus to next submenu item.' + ); + } } } -}); - -ariaTest('ARROW_DOWN moves focus to previous item', exampleFile, 'submenu-up-arrow', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { +); + +ariaTest( + 'ARROW_DOWN moves focus to previous item', + exampleFile, + 'submenu-up-arrow', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Open the submenu - await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Open the submenu + await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { - const item = items[itemIndex]; - const itemText = await item.getText(); - // Account for wrapping - const nextItemIndex = itemIndex === 0 ? ex.numSubmenuItems[menuIndex] - 1 : itemIndex - 1; + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { + const item = items[itemIndex]; + const itemText = await item.getText(); + // Account for wrapping + const nextItemIndex = + itemIndex === 0 ? ex.numSubmenuItems[menuIndex] - 1 : itemIndex - 1; - // Send UP to the item - await item.sendKeys(Key.ARROW_UP); + // Send UP to the item + await item.sendKeys(Key.ARROW_UP); - // Test that the focus is on the previous item - t.true( - await checkFocus(t, ex.allSubmenuItems[menuIndex], nextItemIndex), - 'Sending key "ARROW_UP" to submenu item "' + itemText + '" should send focus to next submenu item.' - ); + // Test that the focus is on the previous item + t.true( + await checkFocus(t, ex.allSubmenuItems[menuIndex], nextItemIndex), + 'Sending key "ARROW_UP" to submenu item "' + + itemText + + '" should send focus to next submenu item.' + ); + } } } -}); - -ariaTest('HOME moves focus to first item', exampleFile, 'submenu-home', async (t) => { - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { +); + +ariaTest( + 'HOME moves focus to first item', + exampleFile, + 'submenu-home', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Open the submenu - await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Open the submenu + await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { - const item = items[itemIndex]; - const itemText = await item.getText(); - // Account for wrapping - const nextItemIndex = itemIndex + 1; + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { + const item = items[itemIndex]; + const itemText = await item.getText(); + // Account for wrapping + const nextItemIndex = itemIndex + 1; - // Send UP to the item - await item.sendKeys(Key.HOME); + // Send UP to the item + await item.sendKeys(Key.HOME); - // Test that the focus is on the first item - t.true( - await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), - 'Sending key "HOME" to submenu item "' + itemText + '" should send focus to first submenu item.' - ); + // Test that the focus is on the first item + t.true( + await checkFocus(t, ex.allSubmenuItems[menuIndex], 0), + 'Sending key "HOME" to submenu item "' + + itemText + + '" should send focus to first submenu item.' + ); + } } } -}); - -ariaTest('END moves focus to last item', exampleFile, 'submenu-end', async (t) => { - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { +); + +ariaTest( + 'END moves focus to last item', + exampleFile, + 'submenu-end', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Open the submenu - await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); - const lastIndex = items.length - 1; + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Open the submenu + await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); + const lastIndex = items.length - 1; - for (let itemIndex = 0; itemIndex < ex.numSubmenuItems[menuIndex]; itemIndex++) { - const item = items[itemIndex]; - const itemText = await item.getText(); - // Account for wrapping - const nextItemIndex = itemIndex + 1; + for ( + let itemIndex = 0; + itemIndex < ex.numSubmenuItems[menuIndex]; + itemIndex++ + ) { + const item = items[itemIndex]; + const itemText = await item.getText(); + // Account for wrapping + const nextItemIndex = itemIndex + 1; - // Send UP to the item - await item.sendKeys(Key.END); + // Send UP to the item + await item.sendKeys(Key.END); - // Test that the focus is on the first item - t.true( - await checkFocus(t, ex.allSubmenuItems[menuIndex], lastIndex), - 'Sending key "END" to submenu item "' + itemText + '" should send focus to last submenu item.' - ); + // Test that the focus is on the first item + t.true( + await checkFocus(t, ex.allSubmenuItems[menuIndex], lastIndex), + 'Sending key "END" to submenu item "' + + itemText + + '" should send focus to last submenu item.' + ); + } } } -}); - -ariaTest('Character sends to menubar changes focus in menubar', - exampleFile, 'submenu-character', async (t) => { - +); +ariaTest( + 'Character sends to menubar changes focus in menubar', + exampleFile, + 'submenu-character', + async (t) => { const charIndexTest = [ - [ // Tests for menu dropdown 0 + [ + // Tests for menu dropdown 0 { sendChar: 's', sendIndex: 0, endIndex: 1 }, { sendChar: 's', sendIndex: 1, endIndex: 0 }, - { sendChar: 'x', sendIndex: 0, endIndex: 0 } + { sendChar: 'x', sendIndex: 0, endIndex: 0 }, ], - [ // Tests for menu dropdown 1 + [ + // Tests for menu dropdown 1 { sendChar: 'u', sendIndex: 0, endIndex: 9 }, - { sendChar: 'y', sendIndex: 9, endIndex: 9 } + { sendChar: 'y', sendIndex: 9, endIndex: 9 }, ], - [ // Tests for menu dropdown 2 + [ + // Tests for menu dropdown 2 { sendChar: 'r', sendIndex: 0, endIndex: 2 }, - { sendChar: 'z', sendIndex: 2, endIndex: 2 } + { sendChar: 'z', sendIndex: 2, endIndex: 2 }, ], - [ // Tests for menu dropdown 3 + [ + // Tests for menu dropdown 3 { sendChar: 'x', sendIndex: 0, endIndex: 2 }, - { sendChar: 'x', sendIndex: 2, endIndex: 6 } - ] + { sendChar: 'x', sendIndex: 2, endIndex: 6 }, + ], ]; - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - // Open the dropdown await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.allSubmenuItems[menuIndex]); + const items = await t.context.queryElements( + t, + ex.allSubmenuItems[menuIndex] + ); for (let test of charIndexTest[menuIndex]) { - // Send character to menuitem const itemText = await items[test.sendIndex].getText(); await items[test.sendIndex].sendKeys(test.sendChar); @@ -1025,8 +1426,14 @@ ariaTest('Character sends to menubar changes focus in menubar', // Test that the focus switches to the appropriate menuitem t.true( await checkFocus(t, ex.allSubmenuItems[menuIndex], test.endIndex), - 'Sending character ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to menuitem ' + + itemText + + ' should move the focus to menuitem ' + + test.endIndex ); } } - }); + } +); diff --git a/test/tests/menubar_menubar-navigation.js b/test/tests/menubar_menubar-navigation.js index c620015b6e..637926fa94 100644 --- a/test/tests/menubar_menubar-navigation.js +++ b/test/tests/menubar_menubar-navigation.js @@ -23,7 +23,7 @@ const ex = { menuMenuitemSelectors: [ '#ex1 [role="menubar"]>li:nth-of-type(1)>[role="menu"]>li>[role="menuitem"]', '#ex1 [role="menubar"]>li:nth-of-type(2)>[role="menu"]>li>[role="menuitem"]', - '#ex1 [role="menubar"]>li:nth-of-type(3)>[role="menu"]>li>[role="menuitem"]' + '#ex1 [role="menubar"]>li:nth-of-type(3)>[role="menu"]>li>[role="menuitem"]', ], groupSelector: '#ex1 [role="group"]', numMenus: 3, @@ -33,63 +33,83 @@ const ex = { // [, ] [0, 2], [0, 3], - [1, 1] + [1, 1], ], - numMenuMenuitems: [4, 6, 8] + numMenuMenuitems: [4, 6, 8], }; // Returns specified submenu const getSubmenuSelector = function (menuIndex, menuitemIndex) { - return '#ex1 [role="menubar"]>li:nth-of-type(' + + return ( + '#ex1 [role="menubar"]>li:nth-of-type(' + (menuIndex + 1) + ')>[role="menu"]>li:nth-of-type(' + (menuitemIndex + 1) + - ')>[role="menu"]'; + ')>[role="menu"]' + ); }; // Returns the menuitems of a specified submenu const getSubmenuMenuitemSelector = function (menuIndex, menuitemIndex) { - return '#ex1 [role="menubar"]>li:nth-of-type(' + + return ( + '#ex1 [role="menubar"]>li:nth-of-type(' + (menuIndex + 1) + ')>[role="menu"]>li:nth-of-type(' + (menuitemIndex + 1) + - ')>[role="menu"] [role="menuitem"]'; + ')>[role="menu"] [role="menuitem"]' + ); }; const openSubmenu = async function (t, menuIndex, menuitemIndex) { // Send ARROW_DOWN to open menu - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); await menubaritems[menuIndex].sendKeys(Key.ARROW_DOWN); // Get the menuitems for that menu and send ARROW_RIGHT to open the submenu - const menuitems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const menuitems = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); await menuitems[menuitemIndex].sendKeys(Key.ARROW_RIGHT); return; }; const waitForUrlChange = async function (t) { - return t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime).catch(() => {}); + return t.context.session + .wait(() => { + return t.context.session.getCurrentUrl().then((url) => { + return url != t.context.url; + }); + }, t.context.waitTime) + .catch(() => {}); }; const exampleInitialized = async function (t) { const initializedSelector = ex.menubarMenuitemSelector + '[tabindex="0"]'; - await t.context.session.wait(async function () { - const els = await t.context.queryElements(t, initializedSelector); - return els.length === 1; - }, t.context.waitTime, 'Timeout waiting for example to initialize'); + await t.context.session.wait( + async function () { + const els = await t.context.queryElements(t, initializedSelector); + return els.length === 1; + }, + t.context.waitTime, + 'Timeout waiting for example to initialize' + ); }; const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const doesMenuitemHaveSubmenu = function (menuIndex, menuitemIndex) { @@ -104,291 +124,451 @@ const doesMenuitemHaveSubmenu = function (menuIndex, menuitemIndex) { return false; }; - // Attributes -ariaTest('Test for role="menubar" on ul', exampleFile, 'menubar-role', async (t) => { +ariaTest( + 'Test for role="menubar" on ul', + exampleFile, + 'menubar-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'menubar', 1, 'ul'); -}); + } +); -ariaTest('Test aria-label on menubar', exampleFile, 'menubar-aria-label', async (t) => { +ariaTest( + 'Test aria-label on menubar', + exampleFile, + 'menubar-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.menubarSelector); -}); - -ariaTest('Test for role="menuitem" in menubar', exampleFile, 'menuitem-role', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - t.is( - menuitems.length, - ex.numMenus, - '"role=menuitem" elements should be found by selector: ' + ex.menubarMenuitemSelector - ); - - for (let menuitem of menuitems) { - t.truthy( - await menuitem.getText(), - '"role=menuitem" elements should all have accessible text content: ' + ex.menubarMenuitemSelector - ); } -}); - -ariaTest('Test roving tabindex', exampleFile, 'menuitem-tabindex', async (t) => { - - - // Wait for roving tabindex to be initialized by the javascript - await exampleInitialized(t); - - await assertRovingTabindex(t, ex.menubarMenuitemSelector, Key.ARROW_RIGHT); -}); - -ariaTest('Test aria-haspopup set to true on menuitems', - exampleFile, 'menuitem-aria-haspopup', async (t) => { - +); + +ariaTest( + 'Test for role="menuitem" in menubar', + exampleFile, + 'menuitem-role', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-haspopup', 'true'); - }); + t.is( + menuitems.length, + ex.numMenus, + '"role=menuitem" elements should be found by selector: ' + + ex.menubarMenuitemSelector + ); -ariaTest('"aria-expanded" attribute on menubar menuitem', exampleFile, 'menuitem-aria-expanded', async (t) => { + for (let menuitem of menuitems) { + t.truthy( + await menuitem.getText(), + '"role=menuitem" elements should all have accessible text content: ' + + ex.menubarMenuitemSelector + ); + } + } +); - // Before interating with page, make sure aria-expanded is set to false - await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-expanded', 'false'); +ariaTest( + 'Test roving tabindex', + exampleFile, + 'menuitem-tabindex', + async (t) => { + // Wait for roving tabindex to be initialized by the javascript + await exampleInitialized(t); - // AND make sure no submenus are visible - const submenus = await t.context.queryElements(t, ex.menuSelector); - for (let submenu of submenus) { - t.false( - await submenu.isDisplayed(), - 'No submenus (found by selector: ' + ex.menuSelector + ') should be displayed on load' + await assertRovingTabindex(t, ex.menubarMenuitemSelector, Key.ARROW_RIGHT); + } +); + +ariaTest( + 'Test aria-haspopup set to true on menuitems', + exampleFile, + 'menuitem-aria-haspopup', + async (t) => { + await assertAttributeValues( + t, + ex.menubarMenuitemSelector, + 'aria-haspopup', + 'true' ); } +); + +ariaTest( + '"aria-expanded" attribute on menubar menuitem', + exampleFile, + 'menuitem-aria-expanded', + async (t) => { + // Before interating with page, make sure aria-expanded is set to false + await assertAttributeValues( + t, + ex.menubarMenuitemSelector, + 'aria-expanded', + 'false' + ); - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + // AND make sure no submenus are visible + const submenus = await t.context.queryElements(t, ex.menuSelector); + for (let submenu of submenus) { + t.false( + await submenu.isDisplayed(), + 'No submenus (found by selector: ' + + ex.menuSelector + + ') should be displayed on load' + ); + } - for (let menuIndex = 0; menuIndex < menuitems.length; menuIndex++) { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Send ARROW_DOWN to open submenu - await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + for (let menuIndex = 0; menuIndex < menuitems.length; menuIndex++) { + // Send ARROW_DOWN to open submenu + await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + + for (let item = 0; item < menuitems.length; item++) { + // Test attribute "aria-expanded" is only set for the opened submenu + const displayed = menuIndex === item ? true : false; + t.is( + await menuitems[item].getAttribute('aria-expanded'), + displayed.toString(), + 'focus is on element ' + + menuIndex + + ' of elements "' + + ex.menubarMenuitemSelector + + '", therefore "aria-expanded" on menuitem ' + + item + + ' should be ' + + displayed + ); - for (let item = 0; item < menuitems.length; item++) { + // Test the submenu is indeed displayed + t.is( + await submenus[item].isDisplayed(), + displayed, + 'focus is on element ' + + menuIndex + + ' of elements "' + + ex.menubarMenuitemSelector + + '", therefore isDisplay of submenu ' + + item + + ' should return ' + + displayed + ); + } - // Test attribute "aria-expanded" is only set for the opened submenu - const displayed = menuIndex === item ? true : false; - t.is( - await menuitems[item].getAttribute('aria-expanded'), - displayed.toString(), - 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + - '", therefore "aria-expanded" on menuitem ' + item + ' should be ' + displayed - ); + // Send the ESCAPE to close submenu + await menuitems[menuIndex].sendKeys(Key.ESCAPE); + } + } +); + +ariaTest( + 'Test for role="none" on menubar li', + exampleFile, + 'none-role', + async (t) => { + const liElements = await t.context.queryElements( + t, + ex.menubarSelector + '>li' + ); - // Test the submenu is indeed displayed + for (let liElement of liElements) { t.is( - await submenus[item].isDisplayed(), - displayed, - 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + - '", therefore isDisplay of submenu ' + item + ' should return ' + displayed + await liElement.getAttribute('role'), + 'none', + '"role=none" should be found on all list elements that are immediate descendants of: ' + + ex.menubarSelector ); } - - // Send the ESCAPE to close submenu - await menuitems[menuIndex].sendKeys(Key.ESCAPE); } -}); - -ariaTest('Test for role="none" on menubar li', exampleFile, 'none-role', async (t) => { - - const liElements = await t.context.queryElements(t, ex.menubarSelector + '>li'); - - for (let liElement of liElements) { - t.is( - await liElement.getAttribute('role'), - 'none', - '"role=none" should be found on all list elements that are immediate descendants of: ' + ex.menubarSelector - ); - } -}); +); ariaTest('Test for role="menu" on ul', exampleFile, 'menu-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'menu', ex.numTotalMenus, 'ul'); + await assertAriaRoles(t, 'ex1', 'menu', ex.numTotalMenus, 'ul'); }); -ariaTest('Test for aria-label on role="menu"', exampleFile, 'menu-aria-label', async (t) => { +ariaTest( + 'Test for aria-label on role="menu"', + exampleFile, + 'menu-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.anyMenuSelector); -}); - -ariaTest('Test for submenu role="menuitem"s with accessible names', exampleFile, 'sub-menuitem-role', async (t) => { - - - const menuitems = await t.context.queryElements(t, ex.anyMenuMenuitemSelector); - - t.truthy( - menuitems.length, - '"role=menuitem" elements should be found by selector: ' + ex.anyMenuMenuitemSelector - ); - - // Test the accessible name of each menuitem - - for (let menuitem of menuitems) { - - // The menuitem is not visible, so we cannot use selenium's "getText" function - const menutext = await t.context.session.executeScript(function () { - const el = arguments[0]; - return el.innerHTML; - }, menuitem); + } +); + +ariaTest( + 'Test for submenu role="menuitem"s with accessible names', + exampleFile, + 'sub-menuitem-role', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.anyMenuMenuitemSelector + ); t.truthy( - menutext, - '"role=menuitem" elements should all have accessible text content: ' + ex.anyMenuMenuitemSelector + menuitems.length, + '"role=menuitem" elements should be found by selector: ' + + ex.anyMenuMenuitemSelector ); - } -}); - -ariaTest('Test tabindex="-1" on submenu role="menuitem"s', exampleFile, 'sub-menuitem-tabindex', async (t) => { - await assertAttributeValues(t, ex.anyMenuMenuitemSelector, 'tabindex', '-1'); -}); - -ariaTest('Test aria-haspopup on menuitems with submenus', exampleFile, 'sub-menuitem-aria-haspopup', async (t) => { - const menubarMenuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + // Test the accessible name of each menuitem - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - const menuItems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - - for (let menuitemIndex = 0; menuitemIndex < menuItems.length; menuitemIndex++) { - const menuitemHasSubmenu = doesMenuitemHaveSubmenu(menuIndex, menuitemIndex); + for (let menuitem of menuitems) { + // The menuitem is not visible, so we cannot use selenium's "getText" function + const menutext = await t.context.session.executeScript(function () { + const el = arguments[0]; + return el.innerHTML; + }, menuitem); - const ariaPopup = menuitemHasSubmenu ? 'true' : null; - const hasAriaPopupMsg = menuitemHasSubmenu ? - 'aria-haspop set to "true".' : - 'no aria-haspop attribute.'; - - t.is( - await menuItems[menuitemIndex].getAttribute('aria-haspopup'), - ariaPopup, - 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + - 'to have ' + hasAriaPopupMsg + t.truthy( + menutext, + '"role=menuitem" elements should all have accessible text content: ' + + ex.anyMenuMenuitemSelector ); } } -}); - -ariaTest('Test aria-expanded on menuitems with submenus', exampleFile, 'sub-menuitem-aria-expanded', async (t) => { - - const menubarMenuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Send ARROW_DOWN to open menu - await menubarMenuitems[menuIndex].sendKeys(Key.ARROW_DOWN); +); + +ariaTest( + 'Test tabindex="-1" on submenu role="menuitem"s', + exampleFile, + 'sub-menuitem-tabindex', + async (t) => { + await assertAttributeValues( + t, + ex.anyMenuMenuitemSelector, + 'tabindex', + '-1' + ); + } +); + +ariaTest( + 'Test aria-haspopup on menuitems with submenus', + exampleFile, + 'sub-menuitem-aria-haspopup', + async (t) => { + const menubarMenuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Get the menuitems for that menu - const menuitems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + const menuItems = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); - // Get the submenu associate with the menuitem we are testing - const submenuSelector = getSubmenuSelector(menuIndex, menuitemIndex); + for ( + let menuitemIndex = 0; + menuitemIndex < menuItems.length; + menuitemIndex++ + ) { + const menuitemHasSubmenu = doesMenuitemHaveSubmenu( + menuIndex, + menuitemIndex + ); - t.is( - await menuitems[menuitemIndex].getAttribute('aria-expanded'), - 'false', - 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + - 'to have aria-expanded="false" after opening the menu that contains it' - ); - t.false( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'submenu attached to menuitem at index ' + menuitemIndex + ' in menu at index ' + - menuIndex + ' is expected to not be displayed after opening the menu that contains the menuitem' + const ariaPopup = menuitemHasSubmenu ? 'true' : null; + const hasAriaPopupMsg = menuitemHasSubmenu + ? 'aria-haspop set to "true".' + : 'no aria-haspop attribute.'; + + t.is( + await menuItems[menuitemIndex].getAttribute('aria-haspopup'), + ariaPopup, + 'menuitem at index ' + + menuitemIndex + + ' in menu at index ' + + menuIndex + + ' is expected ' + + 'to have ' + + hasAriaPopupMsg + ); + } + } + } +); + +ariaTest( + 'Test aria-expanded on menuitems with submenus', + exampleFile, + 'sub-menuitem-aria-expanded', + async (t) => { + const menubarMenuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector ); - // Send ARROW_RIGHT to the menuitem we are testing - await menuitems[menuitemIndex].sendKeys(Key.ARROW_RIGHT); + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - t.is( - await menuitems[menuitemIndex].getAttribute('aria-expanded'), - 'true', - 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + - 'to have aria-expanded="true" after sending right arrow to it' - ); - t.true( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'submenu attached to menuitem at index ' + menuitemIndex + ' in menu at index ' + - menuIndex + ' is expected to be displayed after sending left arrow to associated menuitem' - ); - } + // Send ARROW_DOWN to open menu + await menubarMenuitems[menuIndex].sendKeys(Key.ARROW_DOWN); -}); + // Get the menuitems for that menu + const menuitems = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); -ariaTest('Test for role="none" on menu lis', exampleFile, 'sub-none-role', async (t) => { + // Get the submenu associate with the menuitem we are testing + const submenuSelector = getSubmenuSelector(menuIndex, menuitemIndex); + + t.is( + await menuitems[menuitemIndex].getAttribute('aria-expanded'), + 'false', + 'menuitem at index ' + + menuitemIndex + + ' in menu at index ' + + menuIndex + + ' is expected ' + + 'to have aria-expanded="false" after opening the menu that contains it' + ); + t.false( + await t.context.session + .findElement(By.css(submenuSelector)) + .isDisplayed(), + 'submenu attached to menuitem at index ' + + menuitemIndex + + ' in menu at index ' + + menuIndex + + ' is expected to not be displayed after opening the menu that contains the menuitem' + ); - const liElements = await t.context.queryElements(t, ex.anyMenuSelector + '>li'); + // Send ARROW_RIGHT to the menuitem we are testing + await menuitems[menuitemIndex].sendKeys(Key.ARROW_RIGHT); - for (let liElement of liElements) { - if (await liElement.getAttribute('role') !== 'separator') { t.is( - await liElement.getAttribute('role'), - 'none', - '"role=none" should be found on all list elements that are immediate descendants of: ' + ex.anyMenuSelector + await menuitems[menuitemIndex].getAttribute('aria-expanded'), + 'true', + 'menuitem at index ' + + menuitemIndex + + ' in menu at index ' + + menuIndex + + ' is expected ' + + 'to have aria-expanded="true" after sending right arrow to it' + ); + t.true( + await t.context.session + .findElement(By.css(submenuSelector)) + .isDisplayed(), + 'submenu attached to menuitem at index ' + + menuitemIndex + + ' in menu at index ' + + menuIndex + + ' is expected to be displayed after sending left arrow to associated menuitem' ); } } -}); - - -// KEYS - -ariaTest('Key ENTER open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ENTER key - await menuitems[menuIndex].sendKeys(Key.ENTER); - - // Test that the submenu is displayed - t.true( - await menus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' +); + +ariaTest( + 'Test for role="none" on menu lis', + exampleFile, + 'sub-none-role', + async (t) => { + const liElements = await t.context.queryElements( + t, + ex.anyMenuSelector + '>li' ); - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); + for (let liElement of liElements) { + if ((await liElement.getAttribute('role')) !== 'separator') { + t.is( + await liElement.getAttribute('role'), + 'none', + '"role=none" should be found on all list elements that are immediate descendants of: ' + + ex.anyMenuSelector + ); + } + } } -}); +); +// KEYS -ariaTest('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the SPACE key - await menubaritems[menuIndex].sendKeys(' '); - - // Test that the submenu is displayed - t.true( - await menus[menuIndex].isDisplayed(), - 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should display submenu' +ariaTest( + 'Key ENTER open submenu', + exampleFile, + 'menubar-space-or-enter', + async (t) => { + const menuitems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector ); + const menus = await t.context.queryElements(t, ex.menuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the ENTER key + await menuitems[menuIndex].sendKeys(Key.ENTER); - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); + // Test that the submenu is displayed + t.true( + await menus[menuIndex].isDisplayed(), + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should display submenu' + ); + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' + ); + } } -}); - -ariaTest('Key ARROW_RIGHT moves focus to next menubar item', - exampleFile, 'menubar-right-arrow', async (t) => { +); + +ariaTest( + 'Key SPACE open submenu', + exampleFile, + 'menubar-space-or-enter', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const menus = await t.context.queryElements(t, ex.menuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the SPACE key + await menubaritems[menuIndex].sendKeys(' '); + // Test that the submenu is displayed + t.true( + await menus[menuIndex].isDisplayed(), + 'Sending key "SPACE" to menuitem ' + + menuIndex + + ' in menubar should display submenu' + ); - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), + 'Sending key "SPACE" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' + ); + } + } +); + +ariaTest( + 'Key ARROW_RIGHT moves focus to next menubar item', + exampleFile, + 'menubar-right-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); for (let menuIndex = 0; menuIndex < ex.numMenus + 1; menuIndex++) { - const currentIndex = menuIndex % ex.numMenus; const nextIndex = (menuIndex + 1) % ex.numMenus; @@ -398,16 +578,24 @@ ariaTest('Key ARROW_RIGHT moves focus to next menubar item', // Test the focus is on the next item mod the number of items to account for wrapping t.true( await checkFocus(t, ex.menubarMenuitemSelector, nextIndex), - 'Sending key "ARROW_RIGHT" to menuitem ' + currentIndex + ' should move focus to menuitem ' + nextIndex + 'Sending key "ARROW_RIGHT" to menuitem ' + + currentIndex + + ' should move focus to menuitem ' + + nextIndex ); } - }); - -ariaTest('Key ARROW_RIGHT moves focus to next menubar item', - exampleFile, 'menubar-left-arrow', async (t) => { - - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + } +); + +ariaTest( + 'Key ARROW_RIGHT moves focus to next menubar item', + exampleFile, + 'menubar-left-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); // Send the ARROW_LEFT key to the first menuitem await menubaritems[0].sendKeys(Key.ARROW_LEFT); @@ -418,361 +606,543 @@ ariaTest('Key ARROW_RIGHT moves focus to next menubar item', 'Sending key "ARROW_LEFT" to menuitem 0 will change focus to menu item 3' ); - for (let menuIndex = ex.numMenus - 1; menuIndex > 0; menuIndex--) { - // Send the ARROW_LEFT key await menubaritems[menuIndex].sendKeys(Key.ARROW_LEFT); // Test the focus is on the previous menuitem t.true( await checkFocus(t, ex.menubarMenuitemSelector, menuIndex - 1), - 'Sending key "ARROW_RIGHT" to menuitem ' + menuIndex + ' should move focus to menuitem ' + (menuIndex - 1) + 'Sending key "ARROW_RIGHT" to menuitem ' + + menuIndex + + ' should move focus to menuitem ' + + (menuIndex - 1) ); } - }); - -ariaTest('Key ARROW_UP opens submenu, focus on last item', - exampleFile, 'menubar-up-arrow', async (t) => { - - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + } +); + +ariaTest( + 'Key ARROW_UP opens submenu, focus on last item', + exampleFile, + 'menubar-up-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const menus = await t.context.queryElements(t, ex.menuSelector); for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - // Send the ENTER key await menubaritems[menuIndex].sendKeys(Key.UP); // Test that the submenu is displayed t.true( await menus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should display submenu' ); - const numSubItems = (await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex])).length; + const numSubItems = ( + await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]) + ).length; // Test that the focus is on the last item in the list t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], numSubItems - 1), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + await checkFocus( + t, + ex.menuMenuitemSelectors[menuIndex], + numSubItems - 1 + ), + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' ); } - }); - -ariaTest('Key ARROW_DOWN opens submenu, focus on first item', - exampleFile, 'menubar-down-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + } +); + +ariaTest( + 'Key ARROW_DOWN opens submenu, focus on first item', + exampleFile, + 'menubar-down-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const menus = await t.context.queryElements(t, ex.menuSelector); for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - // Send the ENTER key await menubaritems[menuIndex].sendKeys(Key.DOWN); // Test that the submenu is displayed t.true( await menus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should display submenu' ); // Test that the focus is on the first item in the list t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), + 'Sending key "ENTER" to menuitem ' + + menuIndex + + ' in menubar should send focus to the first element in the submenu' ); } - }); - -ariaTest('Key HOME goes to first item in menubar', exampleFile, 'menubar-home', async (t) => { + } +); + +ariaTest( + 'Key HOME goes to first item in menubar', + exampleFile, + 'menubar-home', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the ARROW_RIGHT key to move the focus to later menu item for every test + for (let i = 0; i < menuIndex; i++) { + await menubaritems[i].sendKeys(Key.ARROW_RIGHT); + } - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the key HOME + await menubaritems[menuIndex].sendKeys(Key.HOME); - // Send the ARROW_RIGHT key to move the focus to later menu item for every test - for (let i = 0; i < menuIndex; i++) { - await menubaritems[i].sendKeys(Key.ARROW_RIGHT); + // Test that the focus is on the first item in the list + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, 0), + 'Sending key "HOME" to menuitem ' + + menuIndex + + ' in menubar should move the foucs to the first menuitem' + ); } - - // Send the key HOME - await menubaritems[menuIndex].sendKeys(Key.HOME); - - // Test that the focus is on the first item in the list - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, 0), - 'Sending key "HOME" to menuitem ' + menuIndex + ' in menubar should move the foucs to the first menuitem' - ); } -}); - -ariaTest('Key END goes to last item in menubar', exampleFile, 'menubar-end', async (t) => { +); + +ariaTest( + 'Key END goes to last item in menubar', + exampleFile, + 'menubar-end', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the ARROW_RIGHT key to move the focus to later menu item for every test + for (let i = 0; i < menuIndex; i++) { + await menubaritems[i].sendKeys(Key.ARROW_RIGHT); + } - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Send the key END + await menubaritems[menuIndex].sendKeys(Key.END); - // Send the ARROW_RIGHT key to move the focus to later menu item for every test - for (let i = 0; i < menuIndex; i++) { - await menubaritems[i].sendKeys(Key.ARROW_RIGHT); + // Test that the focus is on the last item in the list + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), + 'Sending key "END" to menuitem ' + + menuIndex + + ' in menubar should move the foucs to the last menuitem' + ); } - - // Send the key END - await menubaritems[menuIndex].sendKeys(Key.END); - - // Test that the focus is on the last item in the list - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), - 'Sending key "END" to menuitem ' + menuIndex + ' in menubar should move the foucs to the last menuitem' - ); } -}); - -ariaTest('Character sends to menubar changes focus in menubar', - exampleFile, 'menubar-character', async (t) => { - +); +ariaTest( + 'Character sends to menubar changes focus in menubar', + exampleFile, + 'menubar-character', + async (t) => { const charIndexTest = [ { sendChar: 'z', sendIndex: 0, endIndex: 0 }, { sendChar: 'a', sendIndex: 0, endIndex: 1 }, { sendChar: 'a', sendIndex: 1, endIndex: 2 }, - { sendChar: 'a', sendIndex: 2, endIndex: 0 } + { sendChar: 'a', sendIndex: 2, endIndex: 0 }, ]; - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); for (let test of charIndexTest) { - // Send character to menuitem await menubaritems[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate menuitem t.true( - await checkFocus(t, ex.menubarMenuitemSelector, test.endIndex), - 'Sending characther ' + test.sendChar + ' to menuitem ' + test.sendIndex + ' in menubar should move the foucs to menuitem ' + test.endIndex + await checkFocus(t, ex.menubarMenuitemSelector, test.endIndex), + 'Sending characther ' + + test.sendChar + + ' to menuitem ' + + test.sendIndex + + ' in menubar should move the foucs to menuitem ' + + test.endIndex ); } - }); + } +); // This test is failing due to a bug reported in issue: https://github.com/w3c/aria-practices/issues/907 -ariaTest.failing('ENTER in submenu selects item', exampleFile, 'submenu-space-or-enter', async (t) => { - - // Test all the level one menuitems +ariaTest.failing( + 'ENTER in submenu selects item', + exampleFile, + 'submenu-space-or-enter', + async (t) => { + // Test all the level one menuitems - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - await t.context.session.get(t.context.url); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { + await t.context.session.get(t.context.url); + + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); + const itemText = await items[itemIndex].getText(); + + // send ENTER to the item + await items[itemIndex].sendKeys(Key.ENTER); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "ENTER" to menuitem "' + + itemText + + '" should navigate to a new webpage.' + ); + } + } - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); + // Test all the submenu menuitems - // send ENTER to the item - await items[itemIndex].sendKeys(Key.ENTER); - await waitForUrlChange(t); + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "ENTER" to menuitem "' + itemText + '" should navigate to a new webpage.' + // Get the submenu associate with the menuitem we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex ); + const numItems = ( + await t.context.queryElements(t, submenuMenuitemSelector) + ).length; + + // Test all the items in the submenu + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + await openSubmenu(t, ...submenuLocation); + + // send ENTER to the item we are testing + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ENTER); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "ENTER" to menuitem ' + + itemText + + '" should navigate to a new webpage.' + ); + + await t.context.session.get(t.context.url); + } } } +); - // Test all the submenu menuitems - - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; +// This test is failing due to a bug reported in issue: https://github.com/w3c/aria-practices/issues/907 +ariaTest.failing( + 'SPACE in submenu selects item', + exampleFile, + 'submenu-space-or-enter', + async (t) => { + // Test all the level one menuitems - // Get the submenu associate with the menuitem we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const numItems = (await t.context.queryElements(t, submenuMenuitemSelector)).length; + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { + await t.context.session.get(t.context.url); + + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); - // Test all the items in the submenu - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); + const itemText = await items[itemIndex].getText(); + + // send SPACE to the item + await items[itemIndex].sendKeys(' '); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "SPACE" to menuitem "' + + itemText + + '" should navigate to a new webpage.' + ); + } + } - await openSubmenu(t, ...submenuLocation); + // Test all the submenu menuitems - // send ENTER to the item we are testing - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ENTER); - await waitForUrlChange(t); + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "ENTER" to menuitem ' + itemText + '" should navigate to a new webpage.' + // Get the submenu associate with the menuitem we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex ); + const numItems = ( + await t.context.queryElements(t, submenuMenuitemSelector) + ).length; + + // Test all the items in the submenu + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + await openSubmenu(t, ...submenuLocation); + + // send SPACE to the item we are testing + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(' '); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "SPACE" to menuitem ' + + itemText + + '" should navigate to a new webpage.' + ); - await t.context.session.get(t.context.url); + await t.context.session.get(t.context.url); + } } } -}); - -// This test is failing due to a bug reported in issue: https://github.com/w3c/aria-practices/issues/907 -ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or-enter', async (t) => { +); + +ariaTest( + 'ESCAPE to submenu closes submenu', + exampleFile, + 'submenu-escape', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const menus = await t.context.queryElements(t, ex.menuSelector); - // Test all the level one menuitems + // Test all the level one menuitems - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); + const itemText = await items[itemIndex].getText(); - await t.context.session.get(t.context.url); + // send ARROW_RIGHT to the item + await items[itemIndex].sendKeys(Key.ESCAPE); - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send SPACE to the item - await items[itemIndex].sendKeys(' '); - await waitForUrlChange(t); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "SPACE" to menuitem "' + itemText + '" should navigate to a new webpage.' - ); + t.false( + await menus[menuIndex].isDisplayed(), + 'Sending key "ESCAPE" to submenuitem "' + + itemText + + '" should close the menu' + ); + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), + 'Sending key "ESCAPE" to submenuitem "' + + itemText + + '" should change the focus to menuitem ' + + menuIndex + + ' in the menubar' + ); + } } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; + // Test all the submenu menuitems - // Get the submenu associate with the menuitem we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const numItems = (await t.context.queryElements(t, submenuMenuitemSelector)).length; - - // Test all the items in the submenu - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - await openSubmenu(t, ...submenuLocation); - - // send SPACE to the item we are testing - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(' '); - await waitForUrlChange(t); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "SPACE" to menuitem ' + itemText + '" should navigate to a new webpage.' + // Get the submenu items we are testing + let submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex ); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; - await t.context.session.get(t.context.url); - } - } -}); - - -ariaTest('ESCAPE to submenu closes submenu', exampleFile, 'submenu-escape', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + await openSubmenu(t, ...submenuLocation); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); + // send ESCAPE to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ESCAPE); - // send ARROW_RIGHT to the item - await items[itemIndex].sendKeys(Key.ESCAPE); + const submenuSelector = getSubmenuSelector(...submenuLocation); + t.false( + await t.context.session + .findElement(By.css(submenuSelector)) + .isDisplayed(), + 'Sending key "ESCAPE" to submenuitem "' + + itemText + + '" should close the menu' + ); - t.false( - await menus[menuIndex].isDisplayed(), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should close the menu' - ); - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should change the focus to menuitem ' + - menuIndex + ' in the menubar' - ); + t.true( + await checkFocus( + t, + ex.menuMenuitemSelectors[menuIndex], + menuitemIndex + ), + 'Sending key "ESCAPE" to submenuitem "' + + itemText + + '" should send focus to menuitem ' + + menuitemIndex + + ' in the parent menu' + ); + } } } +); + +ariaTest( + 'ARROW_RIGHT to submenu closes submenu and opens next', + exampleFile, + 'submenu-right-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const menus = await t.context.queryElements(t, ex.menuSelector); - // Test all the submenu menuitems - - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - let submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send ESCAPE to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ESCAPE); + // Test all the level one menuitems - const submenuSelector = getSubmenuSelector(...submenuLocation); - t.false( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should close the menu' - ); - - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], menuitemIndex), - 'Sending key "ESCAPE" to submenuitem "' + itemText + - '" should send focus to menuitem ' + menuitemIndex + ' in the parent menu' - ); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); + const itemText = await items[itemIndex].getText(); + const hasSubmenu = await items[itemIndex].getAttribute('aria-haspopup'); + + // send ARROW_RIGHT to the item + await items[itemIndex].sendKeys(Key.ARROW_RIGHT); + + if (hasSubmenu) { + const submenuSelector = getSubmenuSelector(menuIndex, itemIndex); + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + itemIndex + ); + + t.true( + await t.context.session + .findElement(By.css(submenuSelector)) + .isDisplayed(), + 'Sending key "ARROW_RIGHT" to menuitem "' + + itemText + + '" should open the submenu: ' + + submenuSelector + ); + t.true( + await checkFocus(t, submenuMenuitemSelector, 0), + 'Sending key "ARROW_RIGHT" to menuitem "' + + itemIndex + + '" should put focus on first item in submenu: ' + + submenuSelector + ); + } else { + // Account for wrapping (index 0 should go to 3) + const nextMenuIndex = menuIndex === 2 ? 0 : menuIndex + 1; + + // Test that the submenu is closed + t.false( + await menus[menuIndex].isDisplayed(), + 'Sending key "ARROW_RIGHT" to submenuitem "' + + itemText + + '" should close list' + ); + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_RIGHT" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextMenuIndex + + ' in the menubar' + ); + } + } } - } - -}); -ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', exampleFile, 'submenu-right-arrow', async (t) => { + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex + ); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - const hasSubmenu = await items[itemIndex].getAttribute('aria-haspopup'); - - // send ARROW_RIGHT to the item - await items[itemIndex].sendKeys(Key.ARROW_RIGHT); - - if (hasSubmenu) { - const submenuSelector = getSubmenuSelector(menuIndex, itemIndex); - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, itemIndex); + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + await openSubmenu(t, ...submenuLocation); - t.true( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'Sending key "ARROW_RIGHT" to menuitem "' + itemText + - '" should open the submenu: ' + submenuSelector - ); - t.true( - await checkFocus(t, submenuMenuitemSelector, 0), - 'Sending key "ARROW_RIGHT" to menuitem "' + itemIndex + - '" should put focus on first item in submenu: ' + submenuSelector - ); - } - else { + // send ARROW_RIGHT to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_RIGHT); // Account for wrapping (index 0 should go to 3) const nextMenuIndex = menuIndex === 2 ? 0 : menuIndex + 1; @@ -780,269 +1150,321 @@ ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', exampleFile, 's // Test that the submenu is closed t.false( await menus[menuIndex].isDisplayed(), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should close list' + 'Sending key "ARROW_RIGHT" to submenuitem "' + + itemText + + '" should close list' ); // Test that the focus is on the menuitem in the menubar t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_RIGHT" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextMenuIndex + + ' in the menubar' ); } } } +); + +ariaTest( + 'ARROW_LEFT to submenu closes submenu and opens next', + exampleFile, + 'submenu-left-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const menus = await t.context.queryElements(t, ex.menuSelector); - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + // Test all the level one menuitems - await openSubmenu(t, ...submenuLocation); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); + const itemText = await items[itemIndex].getText(); - // send ARROW_RIGHT to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_RIGHT); + // send ARROW_LEFT to the item + await items[itemIndex].sendKeys(Key.ARROW_LEFT); - // Account for wrapping (index 0 should go to 3) - const nextMenuIndex = menuIndex === 2 ? 0 : menuIndex + 1; + // Account for wrapping (index 0 should go to 3) + const nextMenuIndex = menuIndex === 0 ? 2 : menuIndex - 1; - // Test that the submenu is closed - t.false( - await menus[menuIndex].isDisplayed(), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should close list' - ); + // Test that the submenu is closed + t.false( + await menus[menuIndex].isDisplayed(), + 'Sending key "ARROW_LEFT" to submenuitem "' + + itemText + + '" should close list' + ); - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' - ); + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_LEFT" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextMenuIndex + + ' in the menubar' + ); + } } - } -}); - -ariaTest('ARROW_LEFT to submenu closes submenu and opens next', exampleFile, 'submenu-left-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send ARROW_LEFT to the item - await items[itemIndex].sendKeys(Key.ARROW_LEFT); + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - // Account for wrapping (index 0 should go to 3) - const nextMenuIndex = menuIndex === 0 ? 2 : menuIndex - 1; - - // Test that the submenu is closed - t.false( - await menus[menuIndex].isDisplayed(), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should close list' + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex ); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' - ); - } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + await openSubmenu(t, ...submenuLocation); - // send ARROW_LEFT to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_LEFT); + // send ARROW_LEFT to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_LEFT); - const submenuSelector = getSubmenuSelector(...submenuLocation); - t.false( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should close the menu' - ); + const submenuSelector = getSubmenuSelector(...submenuLocation); + t.false( + await t.context.session + .findElement(By.css(submenuSelector)) + .isDisplayed(), + 'Sending key "ARROW_LEFT" to submenuitem "' + + itemText + + '" should close the menu' + ); - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], menuitemIndex), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + - '" should send focus to menuitem ' + menuitemIndex + ' in the parent menu' - ); + t.true( + await checkFocus( + t, + ex.menuMenuitemSelectors[menuIndex], + menuitemIndex + ), + 'Sending key "ARROW_LEFT" to submenuitem "' + + itemText + + '" should send focus to menuitem ' + + menuitemIndex + + ' in the parent menu' + ); + } } } -}); - -ariaTest('ARROW_DOWN moves focus in menu', exampleFile, 'submenu-down-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { +); + +ariaTest( + 'ARROW_DOWN moves focus in menu', + exampleFile, + 'submenu-down-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const menus = await t.context.queryElements(t, ex.menuSelector); - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); + // Test all the level one menuitems - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); + const itemText = await items[itemIndex].getText(); - // send ARROW_DOWN to the item - await items[itemIndex].sendKeys(Key.ARROW_DOWN); + // send ARROW_DOWN to the item + await items[itemIndex].sendKeys(Key.ARROW_DOWN); - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === ex.numMenuMenuitems[menuIndex] - 1 ? - 0 : - itemIndex + 1; + // Account for wrapping (last item should move to first item) + const nextItemIndex = + itemIndex === ex.numMenuMenuitems[menuIndex] - 1 ? 0 : itemIndex + 1; - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], nextItemIndex), - 'Sending key "ARROW_DOWN" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus( + t, + ex.menuMenuitemSelectors[menuIndex], + nextItemIndex + ), + 'Sending key "ARROW_DOWN" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextItemIndex + + ' in the same menu' + ); + } } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex + ); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; - await openSubmenu(t, ...submenuLocation); + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + await openSubmenu(t, ...submenuLocation); - // send ARROW_DOWN to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_DOWN); + // send ARROW_DOWN to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_DOWN); - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === numItems - 1 ? - 0 : - itemIndex + 1; + // Account for wrapping (last item should move to first item) + const nextItemIndex = itemIndex === numItems - 1 ? 0 : itemIndex + 1; - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, submenuMenuitemSelector, nextItemIndex), - 'Sending key "ARROW_DOWN" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, submenuMenuitemSelector, nextItemIndex), + 'Sending key "ARROW_DOWN" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextItemIndex + + ' in the same menu' + ); + } } } -}); - - -ariaTest('ARROW_UP moves focus in menu', exampleFile, 'submenu-up-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { +); + +ariaTest( + 'ARROW_UP moves focus in menu', + exampleFile, + 'submenu-up-arrow', + async (t) => { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + const menus = await t.context.queryElements(t, ex.menuSelector); - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); + // Test all the level one menuitems - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); + const itemText = await items[itemIndex].getText(); - // send ARROW_UP to the item - await items[itemIndex].sendKeys(Key.ARROW_UP); + // send ARROW_UP to the item + await items[itemIndex].sendKeys(Key.ARROW_UP); - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === 0 ? - ex.numMenuMenuitems[menuIndex] - 1 : - itemIndex - 1; + // Account for wrapping (last item should move to first item) + const nextItemIndex = + itemIndex === 0 ? ex.numMenuMenuitems[menuIndex] - 1 : itemIndex - 1; - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], nextItemIndex), - 'Sending key "ARROW_UP" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus( + t, + ex.menuMenuitemSelectors[menuIndex], + nextItemIndex + ), + 'Sending key "ARROW_UP" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextItemIndex + + ' in the same menu' + ); + } } - } - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex + ); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; - await openSubmenu(t, ...submenuLocation); + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + await openSubmenu(t, ...submenuLocation); - // send ARROW_UP to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_UP); + // send ARROW_UP to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_UP); - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === 0 ? - numItems - 1 : - itemIndex - 1; + // Account for wrapping (last item should move to first item) + const nextItemIndex = itemIndex === 0 ? numItems - 1 : itemIndex - 1; - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, submenuMenuitemSelector, nextItemIndex), - 'Sending key "ARROW_UP" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, submenuMenuitemSelector, nextItemIndex), + 'Sending key "ARROW_UP" to submenuitem "' + + itemText + + '" should send focus to menuitem' + + nextItemIndex + + ' in the same menu' + ); + } } } -}); +); ariaTest('HOME moves focus in menu', exampleFile, 'submenu-home', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const menus = await t.context.queryElements(t, ex.menuSelector); // Test all the level one menuitems for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { // Open the submenu await menubaritems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); const itemText = await items[itemIndex].getText(); // send HOME to the item @@ -1051,7 +1473,8 @@ ariaTest('HOME moves focus in menu', exampleFile, 'submenu-home', async (t) => { // Test that the focus is on the menuitem in the menubar t.true( await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "HOME" to submenuitem "' + itemText + + 'Sending key "HOME" to submenuitem "' + + itemText + '" should send focus to first menuitem in the same menu' ); } @@ -1062,12 +1485,14 @@ ariaTest('HOME moves focus in menu', exampleFile, 'submenu-home', async (t) => { const [menuIndex, menuitemIndex] = submenuLocation; // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex + ); const items = await t.context.queryElements(t, submenuMenuitemSelector); const numItems = items.length; for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - await openSubmenu(t, ...submenuLocation); // send HOME to the item @@ -1076,28 +1501,36 @@ ariaTest('HOME moves focus in menu', exampleFile, 'submenu-home', async (t) => { t.true( await checkFocus(t, submenuMenuitemSelector, 0), - 'Sending key "HOME" to submenuitem "' + itemText + + 'Sending key "HOME" to submenuitem "' + + itemText + '" should send focus to the first menuitem in the same menu' ); } } }); - ariaTest('END moves focus in menu', exampleFile, 'submenu-end', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); const menus = await t.context.queryElements(t, ex.menuSelector); // Test all the level one menuitems for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - + for ( + let itemIndex = 0; + itemIndex < ex.numMenuMenuitems[menuIndex]; + itemIndex++ + ) { // Open the submenu await menubaritems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); const itemText = await items[itemIndex].getText(); // send END to the item @@ -1105,8 +1538,13 @@ ariaTest('END moves focus in menu', exampleFile, 'submenu-end', async (t) => { // Test that the focus is on the menuitem in the menubar t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], ex.numMenuMenuitems[menuIndex] - 1), - 'Sending key "END" to submenuitem "' + itemText + + await checkFocus( + t, + ex.menuMenuitemSelectors[menuIndex], + ex.numMenuMenuitems[menuIndex] - 1 + ), + 'Sending key "END" to submenuitem "' + + itemText + '" should send focus to last menuitem in the same menu' ); } @@ -1117,12 +1555,14 @@ ariaTest('END moves focus in menu', exampleFile, 'submenu-end', async (t) => { const [menuIndex, menuitemIndex] = submenuLocation; // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex + ); const items = await t.context.queryElements(t, submenuMenuitemSelector); const numItems = items.length; for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - await openSubmenu(t, ...submenuLocation); // send END to the item @@ -1131,95 +1571,125 @@ ariaTest('END moves focus in menu', exampleFile, 'submenu-end', async (t) => { t.true( await checkFocus(t, submenuMenuitemSelector, numItems - 1), - 'Sending key "END" to submenuitem "' + itemText + + 'Sending key "END" to submenuitem "' + + itemText + '" should send focus to the last menuitem in the same menu' ); } } }); +ariaTest( + 'Character sends to menubar changes focus in menubar', + exampleFile, + 'submenu-character', + async (t) => { + const charIndexTest = [ + [ + // Tests for menu dropdown 0 + { sendChar: 'a', sendIndex: 0, endIndex: 1 }, + { sendChar: 'x', sendIndex: 1, endIndex: 1 }, + { sendChar: 'o', sendIndex: 1, endIndex: 0 }, + ], + [ + // Tests for menu dropdown 1 + { sendChar: 'c', sendIndex: 0, endIndex: 5 }, + { sendChar: 'y', sendIndex: 5, endIndex: 5 }, + ], + [ + // Tests for menu dropdown 2 + { sendChar: 'c', sendIndex: 0, endIndex: 4 }, + { sendChar: 'r', sendIndex: 4, endIndex: 5 }, + { sendChar: 'z', sendIndex: 5, endIndex: 5 }, + ], + ]; -ariaTest('Character sends to menubar changes focus in menubar', exampleFile, 'submenu-character', async (t) => { - - const charIndexTest = [ - [ // Tests for menu dropdown 0 - { sendChar: 'a', sendIndex: 0, endIndex: 1 }, - { sendChar: 'x', sendIndex: 1, endIndex: 1 }, - { sendChar: 'o', sendIndex: 1, endIndex: 0 } - ], - [ // Tests for menu dropdown 1 - { sendChar: 'c', sendIndex: 0, endIndex: 5 }, - { sendChar: 'y', sendIndex: 5, endIndex: 5 } - ], - [ // Tests for menu dropdown 2 - { sendChar: 'c', sendIndex: 0, endIndex: 4 }, - { sendChar: 'r', sendIndex: 4, endIndex: 5 }, - { sendChar: 'z', sendIndex: 5, endIndex: 5 } - ] - ]; - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Open the dropdown - await menubaritems[menuIndex].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - - for (let test of charIndexTest[menuIndex]) { + const menubaritems = await t.context.queryElements( + t, + ex.menubarMenuitemSelector + ); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + // Open the dropdown + await menubaritems[menuIndex].sendKeys(Key.ARROW_DOWN); + const items = await t.context.queryElements( + t, + ex.menuMenuitemSelectors[menuIndex] + ); - // Send character to menuitem - const itemText = await items[test.sendIndex].getText(); - await items[test.sendIndex].sendKeys(test.sendChar); + for (let test of charIndexTest[menuIndex]) { + // Send character to menuitem + const itemText = await items[test.sendIndex].getText(); + await items[test.sendIndex].sendKeys(test.sendChar); - // Test that the focus switches to the appropriate menuitem - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], test.endIndex), - 'Sending characther ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex - ); + // Test that the focus switches to the appropriate menuitem + t.true( + await checkFocus( + t, + ex.menuMenuitemSelectors[menuIndex], + test.endIndex + ), + 'Sending characther ' + + test.sendChar + + ' to menuitem ' + + itemText + + ' should move the focus to menuitem ' + + test.endIndex + ); + } } - } - const subCharIndexTest = [ - [ // Tests for menu dropdown 0 - { sendChar: 'c', sendIndex: 0, endIndex: 1 }, - { sendChar: 'h', sendIndex: 1, endIndex: 0 }, - { sendChar: 'x', sendIndex: 0, endIndex: 0 } - ], - [ // Tests for menu dropdown 1 - { sendChar: 'f', sendIndex: 0, endIndex: 1 }, - { sendChar: 'f', sendIndex: 1, endIndex: 2 } - ], - [ // Tests for menu dropdown 2 - { sendChar: 'p', sendIndex: 0, endIndex: 2 }, - { sendChar: 'z', sendIndex: 2, endIndex: 2 } - ] - ]; - - let testIndex = 0; + const subCharIndexTest = [ + [ + // Tests for menu dropdown 0 + { sendChar: 'c', sendIndex: 0, endIndex: 1 }, + { sendChar: 'h', sendIndex: 1, endIndex: 0 }, + { sendChar: 'x', sendIndex: 0, endIndex: 0 }, + ], + [ + // Tests for menu dropdown 1 + { sendChar: 'f', sendIndex: 0, endIndex: 1 }, + { sendChar: 'f', sendIndex: 1, endIndex: 2 }, + ], + [ + // Tests for menu dropdown 2 + { sendChar: 'p', sendIndex: 0, endIndex: 2 }, + { sendChar: 'z', sendIndex: 2, endIndex: 2 }, + ], + ]; - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; + let testIndex = 0; - await openSubmenu(t, ...submenuLocation); + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); + await openSubmenu(t, ...submenuLocation); - for (let test of subCharIndexTest[testIndex]) { + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector( + menuIndex, + menuitemIndex + ); + const items = await t.context.queryElements(t, submenuMenuitemSelector); - // Send character to menuitem - const itemText = await items[test.sendIndex].getText(); - await items[test.sendIndex].sendKeys(test.sendChar); + for (let test of subCharIndexTest[testIndex]) { + // Send character to menuitem + const itemText = await items[test.sendIndex].getText(); + await items[test.sendIndex].sendKeys(test.sendChar); - // Test that the focus switches to the appropriate menuitem - t.true( - await checkFocus(t, submenuMenuitemSelector, test.endIndex), - 'Sending characther ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex - ); - } + // Test that the focus switches to the appropriate menuitem + t.true( + await checkFocus(t, submenuMenuitemSelector, test.endIndex), + 'Sending characther ' + + test.sendChar + + ' to menuitem ' + + itemText + + ' should move the focus to menuitem ' + + test.endIndex + ); + } - testIndex++; + testIndex++; + } } -}); +); diff --git a/test/tests/meter_meter.js b/test/tests/meter_meter.js index bde3005ee7..cc560fc609 100644 --- a/test/tests/meter_meter.js +++ b/test/tests/meter_meter.js @@ -9,66 +9,111 @@ const exampleFile = 'meter/meter.html'; const ex = { meterSelector: '#example [role="meter"]', - fillSelector: '#example [role="meter"] > svg' + fillSelector: '#example [role="meter"] > svg', }; // Attributes -ariaTest('role="meter" element exists', exampleFile, 'meter-role', async (t) => { - await assertAriaRoles(t, 'example', 'meter', '1', 'div'); -}); +ariaTest( + 'role="meter" element exists', + exampleFile, + 'meter-role', + async (t) => { + await assertAriaRoles(t, 'example', 'meter', '1', 'div'); + } +); -ariaTest('"aria-labelledby" attribute', exampleFile, 'meter-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.meterSelector); -}); +ariaTest( + '"aria-labelledby" attribute', + exampleFile, + 'meter-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.meterSelector); + } +); -ariaTest('"aria-valuemin" attribute', exampleFile, 'meter-aria-valuemin', async (t) => { - const meter = await t.context.session.findElement(By.css(ex.meterSelector)); - const valuemin = await meter.getAttribute('aria-valuemin'); +ariaTest( + '"aria-valuemin" attribute', + exampleFile, + 'meter-aria-valuemin', + async (t) => { + const meter = await t.context.session.findElement(By.css(ex.meterSelector)); + const valuemin = await meter.getAttribute('aria-valuemin'); - t.is(typeof valuemin, 'string', 'aria-valuemin is present on the meter'); - t.false(isNaN(parseFloat(valuemin)), 'aria-valuemin is a number'); -}); + t.is(typeof valuemin, 'string', 'aria-valuemin is present on the meter'); + t.false(isNaN(parseFloat(valuemin)), 'aria-valuemin is a number'); + } +); -ariaTest('"aria-valuemax" attribute', exampleFile, 'meter-aria-valuemax', async (t) => { - const meter = await t.context.session.findElement(By.css(ex.meterSelector)); - const [valuemax, valuemin] = await Promise.all([ - meter.getAttribute('aria-valuemax'), - meter.getAttribute('aria-valuemin') - ]); +ariaTest( + '"aria-valuemax" attribute', + exampleFile, + 'meter-aria-valuemax', + async (t) => { + const meter = await t.context.session.findElement(By.css(ex.meterSelector)); + const [valuemax, valuemin] = await Promise.all([ + meter.getAttribute('aria-valuemax'), + meter.getAttribute('aria-valuemin'), + ]); - t.is(typeof valuemax, 'string', 'aria-valuemax is present on the meter'); - t.false(isNaN(parseFloat(valuemax)), 'aria-valuemax is a number'); - t.true(parseFloat(valuemax) >= parseFloat(valuemin), 'max value is greater than min value'); -}); + t.is(typeof valuemax, 'string', 'aria-valuemax is present on the meter'); + t.false(isNaN(parseFloat(valuemax)), 'aria-valuemax is a number'); + t.true( + parseFloat(valuemax) >= parseFloat(valuemin), + 'max value is greater than min value' + ); + } +); -ariaTest('"aria-valuenow" attribute', exampleFile, 'meter-aria-valuenow', async (t) => { - const meter = await t.context.session.findElement(By.css(ex.meterSelector)); - const [valuenow, valuemax, valuemin] = await Promise.all([ - meter.getAttribute('aria-valuenow'), - meter.getAttribute('aria-valuemax'), - meter.getAttribute('aria-valuemin') - ]); +ariaTest( + '"aria-valuenow" attribute', + exampleFile, + 'meter-aria-valuenow', + async (t) => { + const meter = await t.context.session.findElement(By.css(ex.meterSelector)); + const [valuenow, valuemax, valuemin] = await Promise.all([ + meter.getAttribute('aria-valuenow'), + meter.getAttribute('aria-valuemax'), + meter.getAttribute('aria-valuemin'), + ]); - t.is(typeof valuenow, 'string', 'aria-valuenow is present on the meter'); - t.false(isNaN(parseFloat(valuenow)), 'aria-valuenow is a number'); - t.true(parseFloat(valuenow) >= parseFloat(valuemin), 'current value is greater than min value'); - t.true(parseFloat(valuenow) <= parseFloat(valuemax), 'current value is less than max value'); -}); + t.is(typeof valuenow, 'string', 'aria-valuenow is present on the meter'); + t.false(isNaN(parseFloat(valuenow)), 'aria-valuenow is a number'); + t.true( + parseFloat(valuenow) >= parseFloat(valuemin), + 'current value is greater than min value' + ); + t.true( + parseFloat(valuenow) <= parseFloat(valuemax), + 'current value is less than max value' + ); + } +); -ariaTest('fill matches current value', exampleFile, 'meter-aria-valuenow', async (t) => { - const meter = await t.context.session.findElement(By.css(ex.meterSelector)); - const fill = await t.context.session.findElement(By.css(ex.fillSelector)); - const [valuenow, valuemax, valuemin] = await Promise.all([ - meter.getAttribute('aria-valuenow'), - meter.getAttribute('aria-valuemax'), - meter.getAttribute('aria-valuemin') - ]); +ariaTest( + 'fill matches current value', + exampleFile, + 'meter-aria-valuenow', + async (t) => { + const meter = await t.context.session.findElement(By.css(ex.meterSelector)); + const fill = await t.context.session.findElement(By.css(ex.fillSelector)); + const [valuenow, valuemax, valuemin] = await Promise.all([ + meter.getAttribute('aria-valuenow'), + meter.getAttribute('aria-valuemax'), + meter.getAttribute('aria-valuemin'), + ]); - const currentPercent = (valuenow - valuemin) / (valuemax - valuemin); - const [fillSize, meterSize] = await Promise.all([fill.getRect(), meter.getRect()]); + const currentPercent = (valuenow - valuemin) / (valuemax - valuemin); + const [fillSize, meterSize] = await Promise.all([ + fill.getRect(), + meter.getRect(), + ]); - // fudging a little here, since meter has 8px total border + padding - // would be better in a unit test eventually - const expectedFillWidth = (meterSize.width - 8) * currentPercent; - t.true(Math.abs(expectedFillWidth - fillSize.width) < 10, 'Fill width is the correct percent of meter, +/- 10px'); -}); + // fudging a little here, since meter has 8px total border + padding + // would be better in a unit test eventually + const expectedFillWidth = (meterSize.width - 8) * currentPercent; + t.true( + Math.abs(expectedFillWidth - fillSize.width) < 10, + 'Fill width is the correct percent of meter, +/- 10px' + ); + } +); diff --git a/test/tests/radio_radio-activedescendant.js b/test/tests/radio_radio-activedescendant.js index caa6d9d608..05bde348be 100644 --- a/test/tests/radio_radio-activedescendant.js +++ b/test/tests/radio_radio-activedescendant.js @@ -15,196 +15,308 @@ const ex = { radioSelector: '#ex1 [role="radio"]', radiogroupSelectors: [ '#ex1 [role="radiogroup"]:nth-of-type(1)', - '#ex1 [role="radiogroup"]:nth-of-type(2)' + '#ex1 [role="radiogroup"]:nth-of-type(2)', ], radioSelectors: [ '#ex1 [role="radiogroup"]:nth-of-type(1) [role="radio"]', - '#ex1 [role="radiogroup"]:nth-of-type(2) [role="radio"]' + '#ex1 [role="radiogroup"]:nth-of-type(2) [role="radio"]', ], - innerRadioSelector: '[role="radio"]' + innerRadioSelector: '[role="radio"]', }; // Attributes -ariaTest('role="radiogroup" on div element', exampleFile, 'radiogroup-role', async (t) => { +ariaTest( + 'role="radiogroup" on div element', + exampleFile, + 'radiogroup-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'radiogroup', '2', 'ul'); -}); - -ariaTest('"aria-labelledby" attribute on radiogroup', exampleFile, 'radiogroup-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.radiogroupSelector); -}); + } +); + +ariaTest( + '"aria-labelledby" attribute on radiogroup', + exampleFile, + 'radiogroup-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.radiogroupSelector); + } +); -ariaTest('tabindex on radiogroup', exampleFile, 'radiogroup-tabindex', async (t) => { +ariaTest( + 'tabindex on radiogroup', + exampleFile, + 'radiogroup-tabindex', + async (t) => { await assertAttributeValues(t, ex.radiogroupSelector, 'tabindex', '0'); -}); - -ariaTest('aria-activedescendant', exampleFile, 'radiogroup-aria-activedescendant', async (t) => { - await assertAttributeValues(t, ex.radiogroupSelectors[0], 'aria-activedescendant', 'rb11'); - await assertAttributeValues(t, ex.radiogroupSelectors[1], 'aria-activedescendant', 'rb21'); -}); + } +); + +ariaTest( + 'aria-activedescendant', + exampleFile, + 'radiogroup-aria-activedescendant', + async (t) => { + await assertAttributeValues( + t, + ex.radiogroupSelectors[0], + 'aria-activedescendant', + 'rb11' + ); + await assertAttributeValues( + t, + ex.radiogroupSelectors[1], + 'aria-activedescendant', + 'rb21' + ); + } +); -ariaTest('role="radio" on div elements', exampleFile, 'radio-role', async (t) => { +ariaTest( + 'role="radio" on div elements', + exampleFile, + 'radio-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'radio', '6', 'li'); -}); - -ariaTest('"aria-checked" set on role="radio"', exampleFile, 'radio-aria-checked', async (t) => { - - // The radio groups will be all unchecked on page load - await assertAttributeValues(t, ex.radioSelector, 'aria-checked', 'false'); - - const radiogroups = await t.context.queryElements(t, ex.radiogroupSelector); - for (let radiogroup of radiogroups) { - const radios = await t.context.queryElements(t, ex.innerRadioSelector, radiogroup); - - for (let checked = 0; checked < radios.length; checked++) { - - await radios[checked].click(); - for (let el = 0; el < radios.length; el++) { + } +); + +ariaTest( + '"aria-checked" set on role="radio"', + exampleFile, + 'radio-aria-checked', + async (t) => { + // The radio groups will be all unchecked on page load + await assertAttributeValues(t, ex.radioSelector, 'aria-checked', 'false'); + + const radiogroups = await t.context.queryElements(t, ex.radiogroupSelector); + for (let radiogroup of radiogroups) { + const radios = await t.context.queryElements( + t, + ex.innerRadioSelector, + radiogroup + ); - // test only one element has aria-checked="true" - const isChecked = el === checked ? 'true' : 'false'; - t.is( - await radios[el].getAttribute('aria-checked'), - isChecked, - 'Tab at index ' + checked + ' is checked, therefore, tab at index ' + - el + ' should have aria-checked="' + checked + '"' - ); + for (let checked = 0; checked < radios.length; checked++) { + await radios[checked].click(); + for (let el = 0; el < radios.length; el++) { + // test only one element has aria-checked="true" + const isChecked = el === checked ? 'true' : 'false'; + t.is( + await radios[el].getAttribute('aria-checked'), + isChecked, + 'Tab at index ' + + checked + + ' is checked, therefore, tab at index ' + + el + + ' should have aria-checked="' + + checked + + '"' + ); + } } } } -}); +); // Keys -ariaTest('Moves focus to first or checked item', exampleFile, 'key-tab', async (t) => { - - // On page load, the first item in the radio list should be in tab index - await assertTabOrder(t, [ - ex.radiogroupSelectors[0], - ex.radiogroupSelectors[1] - ]); - - // Click the second item in each radio list - await t.context.session - .findElement(By.css(ex.radioSelectors[0] + ':nth-of-type(2)')).click(); - await t.context.session - .findElement(By.css(ex.radioSelectors[1] + ':nth-of-type(2)')).click(); - - // Now the second radio item in each list should be selected, but tab will take - // the focus between the radio groups - await assertTabOrder(t, [ - ex.radiogroupSelectors[0], - ex.radiogroupSelectors[1] - ]); -}); +ariaTest( + 'Moves focus to first or checked item', + exampleFile, + 'key-tab', + async (t) => { + // On page load, the first item in the radio list should be in tab index + await assertTabOrder(t, [ + ex.radiogroupSelectors[0], + ex.radiogroupSelectors[1], + ]); + + // Click the second item in each radio list + await t.context.session + .findElement(By.css(ex.radioSelectors[0] + ':nth-of-type(2)')) + .click(); + await t.context.session + .findElement(By.css(ex.radioSelectors[1] + ':nth-of-type(2)')) + .click(); + + // Now the second radio item in each list should be selected, but tab will take + // the focus between the radio groups + await assertTabOrder(t, [ + ex.radiogroupSelectors[0], + ex.radiogroupSelectors[1], + ]); + } +); ariaTest('Selects radio item', exampleFile, 'key-space', async (t) => { - - await t.context.session.findElement(By.css(ex.radiogroupSelectors[0])).sendKeys(' '); + await t.context.session + .findElement(By.css(ex.radiogroupSelectors[0])) + .sendKeys(' '); const firstCrustRadioOption = ex.radioSelectors[0] + ':nth-of-type(1)'; await assertAttributeValues(t, firstCrustRadioOption, 'aria-checked', 'true'); - await t.context.session.findElement(By.css(ex.radiogroupSelectors[1])).sendKeys(' '); + await t.context.session + .findElement(By.css(ex.radiogroupSelectors[1])) + .sendKeys(' '); const firstDeliveryRadioOption = ex.radioSelectors[1] + ':nth-of-type(1)'; - await assertAttributeValues(t, firstDeliveryRadioOption, 'aria-checked', 'true'); + await assertAttributeValues( + t, + firstDeliveryRadioOption, + 'aria-checked', + 'true' + ); }); -ariaTest('RIGHT ARROW changes focus and checks radio', exampleFile, 'key-down-right-arrow', async (t) => { - - for (let groupIndex = 0; groupIndex < 2; groupIndex++) { - - const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; - const radioSelector = ex.radioSelectors[groupIndex]; - const radiogroup = await t.context.session.findElement(By.css(radiogroupSelector)); - const radios = await t.context.queryElements(t, radioSelector); - - // Right arrow moves focus to right - for (let index = 0; index < radios.length; index++) { - await radiogroup.sendKeys(Key.ARROW_RIGHT); - const newindex = (index + 1) % radios.length; - - await assertAriaActivedescendant(t, radiogroupSelector, radioSelector, newindex); - t.is( - await radios[newindex].getAttribute('aria-checked'), - 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_RIGHT to radio' + - ' at index ' + index +ariaTest( + 'RIGHT ARROW changes focus and checks radio', + exampleFile, + 'key-down-right-arrow', + async (t) => { + for (let groupIndex = 0; groupIndex < 2; groupIndex++) { + const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; + const radioSelector = ex.radioSelectors[groupIndex]; + const radiogroup = await t.context.session.findElement( + By.css(radiogroupSelector) ); + const radios = await t.context.queryElements(t, radioSelector); + + // Right arrow moves focus to right + for (let index = 0; index < radios.length; index++) { + await radiogroup.sendKeys(Key.ARROW_RIGHT); + const newindex = (index + 1) % radios.length; + + await assertAriaActivedescendant( + t, + radiogroupSelector, + radioSelector, + newindex + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_RIGHT to radio' + + ' at index ' + + index + ); + } } } -}); - -ariaTest('DOWN ARROW changes focus and checks radio', exampleFile, 'key-down-right-arrow', async (t) => { - - for (let groupIndex = 0; groupIndex < 2; groupIndex++) { - - const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; - const radioSelector = ex.radioSelectors[groupIndex]; - const radiogroup = await t.context.session.findElement(By.css(radiogroupSelector)); - const radios = await t.context.queryElements(t, radioSelector); - - // Down arrow moves focus to right - for (let index = 0; index < radios.length; index++) { - await radiogroup.sendKeys(Key.ARROW_DOWN); - const newindex = (index + 1) % radios.length; - - await assertAriaActivedescendant(t, radiogroupSelector, radioSelector, newindex); - t.is( - await radios[newindex].getAttribute('aria-checked'), - 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_DOWN to radio' + - ' at index ' + index +); + +ariaTest( + 'DOWN ARROW changes focus and checks radio', + exampleFile, + 'key-down-right-arrow', + async (t) => { + for (let groupIndex = 0; groupIndex < 2; groupIndex++) { + const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; + const radioSelector = ex.radioSelectors[groupIndex]; + const radiogroup = await t.context.session.findElement( + By.css(radiogroupSelector) ); + const radios = await t.context.queryElements(t, radioSelector); + + // Down arrow moves focus to right + for (let index = 0; index < radios.length; index++) { + await radiogroup.sendKeys(Key.ARROW_DOWN); + const newindex = (index + 1) % radios.length; + + await assertAriaActivedescendant( + t, + radiogroupSelector, + radioSelector, + newindex + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_DOWN to radio' + + ' at index ' + + index + ); + } } } -}); - -ariaTest('LEFT ARROW changes focus and checks radio', exampleFile, 'key-up-left-arrow', async (t) => { - - for (let groupIndex = 0; groupIndex < 2; groupIndex++) { - - const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; - const radioSelector = ex.radioSelectors[groupIndex]; - const radiogroup = await t.context.session.findElement(By.css(radiogroupSelector)); - const radios = await t.context.queryElements(t, radioSelector); - - // Left arrow moves focus to left - for (let index of [0, 2, 1]) { - await radiogroup.sendKeys(Key.ARROW_LEFT); - const newindex = index - 1 === -1 ? 2 : index - 1; - - await assertAriaActivedescendant(t, radiogroupSelector, radioSelector, newindex); - t.is( - await radios[newindex].getAttribute('aria-checked'), - 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_LEFT to radio' + - ' at index ' + index +); + +ariaTest( + 'LEFT ARROW changes focus and checks radio', + exampleFile, + 'key-up-left-arrow', + async (t) => { + for (let groupIndex = 0; groupIndex < 2; groupIndex++) { + const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; + const radioSelector = ex.radioSelectors[groupIndex]; + const radiogroup = await t.context.session.findElement( + By.css(radiogroupSelector) ); + const radios = await t.context.queryElements(t, radioSelector); + + // Left arrow moves focus to left + for (let index of [0, 2, 1]) { + await radiogroup.sendKeys(Key.ARROW_LEFT); + const newindex = index - 1 === -1 ? 2 : index - 1; + + await assertAriaActivedescendant( + t, + radiogroupSelector, + radioSelector, + newindex + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_LEFT to radio' + + ' at index ' + + index + ); + } } } -}); - -ariaTest('UP ARROW changes focus and checks radio', exampleFile, 'key-up-left-arrow', async (t) => { - - for (let groupIndex = 0; groupIndex < 2; groupIndex++) { - - const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; - const radioSelector = ex.radioSelectors[groupIndex]; - const radiogroup = await t.context.session.findElement(By.css(radiogroupSelector)); - const radios = await t.context.queryElements(t, radioSelector); - - // Up arrow moves focus to up - for (let index of [0, 2, 1]) { - await radiogroup.sendKeys(Key.ARROW_UP); - const newindex = index - 1 === -1 ? 2 : index - 1; - - await assertAriaActivedescendant(t, radiogroupSelector, radioSelector, newindex); - t.is( - await radios[newindex].getAttribute('aria-checked'), - 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_UP to radio' + - ' at index ' + index +); + +ariaTest( + 'UP ARROW changes focus and checks radio', + exampleFile, + 'key-up-left-arrow', + async (t) => { + for (let groupIndex = 0; groupIndex < 2; groupIndex++) { + const radiogroupSelector = ex.radiogroupSelectors[groupIndex]; + const radioSelector = ex.radioSelectors[groupIndex]; + const radiogroup = await t.context.session.findElement( + By.css(radiogroupSelector) ); + const radios = await t.context.queryElements(t, radioSelector); + + // Up arrow moves focus to up + for (let index of [0, 2, 1]) { + await radiogroup.sendKeys(Key.ARROW_UP); + const newindex = index - 1 === -1 ? 2 : index - 1; + + await assertAriaActivedescendant( + t, + radiogroupSelector, + radioSelector, + newindex + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_UP to radio' + + ' at index ' + + index + ); + } } } -}); +); diff --git a/test/tests/radio_radio.js b/test/tests/radio_radio.js index b1920a497c..8b353c8015 100644 --- a/test/tests/radio_radio.js +++ b/test/tests/radio_radio.js @@ -15,242 +15,334 @@ const ex = { radioSelector: '#ex1 [role="radio"]', innerRadioSelector: '[role="radio"]', crustRadioSelector: '#ex1 [role="radiogroup"]:nth-of-type(1) [role="radio"]', - deliveryRadioSelector: '#ex1 [role="radiogroup"]:nth-of-type(2) [role="radio"]' + deliveryRadioSelector: + '#ex1 [role="radiogroup"]:nth-of-type(2) [role="radio"]', }; const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; // Attributes -ariaTest('role="radiogroup" on div element', exampleFile, 'radiogroup-role', async (t) => { +ariaTest( + 'role="radiogroup" on div element', + exampleFile, + 'radiogroup-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'radiogroup', '2', 'div'); -}); - -ariaTest('"aria-labelledby" attribute on radiogroup', exampleFile, 'radiogroup-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.radiogroupSelector); -}); + } +); + +ariaTest( + '"aria-labelledby" attribute on radiogroup', + exampleFile, + 'radiogroup-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.radiogroupSelector); + } +); -ariaTest('role="radio" on div elements', exampleFile, 'radio-role', async (t) => { +ariaTest( + 'role="radio" on div elements', + exampleFile, + 'radio-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'radio', '6', 'div'); -}); - -ariaTest('roving tabindex on radio elements', exampleFile, 'radio-tabindex', async (t) => { - - // Test first radio group - let radios = ex.radiogroupSelector + ':nth-of-type(1) ' + ex.innerRadioSelector; - await assertRovingTabindex(t, radios, Key.ARROW_RIGHT); - - // Test second radio group - radios = ex.radiogroupSelector + ':nth-of-type(2) ' + ex.innerRadioSelector; - await assertRovingTabindex(t, radios, Key.ARROW_RIGHT); -}); - -ariaTest('"aria-checked" set on role="radio"', exampleFile, 'radio-aria-checked', async (t) => { - - // The radio groups will be all unchecked on page load - await assertAttributeValues(t, ex.radioSelector, 'aria-checked', 'false'); - - const radiogroups = await t.context.queryElements(t, ex.radiogroupSelector); - for (let radiogroup of radiogroups) { - let radios = await t.context.queryElements(t, ex.innerRadioSelector, radiogroup); - - for (let checked = 0; checked < radios.length; checked++) { - - await radios[checked].click(); - for (let el = 0; el < radios.length; el++) { - - // test only one element has aria-checked="true" - let isChecked = el === checked ? 'true' : 'false'; - t.is( - await radios[el].getAttribute('aria-checked'), - isChecked, - 'Tab at index ' + checked + ' is checked, therefore, tab at index ' + - el + ' should have aria-checked="' + checked + '"' - ); + } +); + +ariaTest( + 'roving tabindex on radio elements', + exampleFile, + 'radio-tabindex', + async (t) => { + // Test first radio group + let radios = + ex.radiogroupSelector + ':nth-of-type(1) ' + ex.innerRadioSelector; + await assertRovingTabindex(t, radios, Key.ARROW_RIGHT); + + // Test second radio group + radios = ex.radiogroupSelector + ':nth-of-type(2) ' + ex.innerRadioSelector; + await assertRovingTabindex(t, radios, Key.ARROW_RIGHT); + } +); + +ariaTest( + '"aria-checked" set on role="radio"', + exampleFile, + 'radio-aria-checked', + async (t) => { + // The radio groups will be all unchecked on page load + await assertAttributeValues(t, ex.radioSelector, 'aria-checked', 'false'); + + const radiogroups = await t.context.queryElements(t, ex.radiogroupSelector); + for (let radiogroup of radiogroups) { + let radios = await t.context.queryElements( + t, + ex.innerRadioSelector, + radiogroup + ); + + for (let checked = 0; checked < radios.length; checked++) { + await radios[checked].click(); + for (let el = 0; el < radios.length; el++) { + // test only one element has aria-checked="true" + let isChecked = el === checked ? 'true' : 'false'; + t.is( + await radios[el].getAttribute('aria-checked'), + isChecked, + 'Tab at index ' + + checked + + ' is checked, therefore, tab at index ' + + el + + ' should have aria-checked="' + + checked + + '"' + ); + } } } } -}); +); // Keys -ariaTest('Moves focus to first or checked item', exampleFile, 'key-tab', async (t) => { - - // On page load, the first item in the radio list should be in tab index - await assertTabOrder(t, [ - ex.crustRadioSelector + ':nth-of-type(1)', - ex.deliveryRadioSelector + ':nth-of-type(1)' - ]); - - // Click the second item in each radio list, to set roving tab index - await t.context.session - .findElement(By.css(ex.crustRadioSelector + ':nth-of-type(2)')).click(); - await t.context.session - .findElement(By.css(ex.deliveryRadioSelector + ':nth-of-type(2)')).click(); - - // Now the second radio item in each list should be selected - await assertTabOrder(t, [ - ex.crustRadioSelector + ':nth-of-type(2)', - ex.deliveryRadioSelector + ':nth-of-type(2)' - ]); -}); +ariaTest( + 'Moves focus to first or checked item', + exampleFile, + 'key-tab', + async (t) => { + // On page load, the first item in the radio list should be in tab index + await assertTabOrder(t, [ + ex.crustRadioSelector + ':nth-of-type(1)', + ex.deliveryRadioSelector + ':nth-of-type(1)', + ]); + + // Click the second item in each radio list, to set roving tab index + await t.context.session + .findElement(By.css(ex.crustRadioSelector + ':nth-of-type(2)')) + .click(); + await t.context.session + .findElement(By.css(ex.deliveryRadioSelector + ':nth-of-type(2)')) + .click(); + + // Now the second radio item in each list should be selected + await assertTabOrder(t, [ + ex.crustRadioSelector + ':nth-of-type(2)', + ex.deliveryRadioSelector + ':nth-of-type(2)', + ]); + } +); ariaTest('Selects radio item', exampleFile, 'key-space', async (t) => { - const firstCrustSelector = ex.crustRadioSelector + ':nth-of-type(1)'; await t.context.session.findElement(By.css(firstCrustSelector)).sendKeys(' '); await assertAttributeValues(t, firstCrustSelector, 'aria-checked', 'true'); const firstDeliverySelector = ex.deliveryRadioSelector + ':nth-of-type(1)'; - await t.context.session.findElement(By.css(firstDeliverySelector)).sendKeys(' '); + await t.context.session + .findElement(By.css(firstDeliverySelector)) + .sendKeys(' '); await assertAttributeValues(t, firstDeliverySelector, 'aria-checked', 'true'); - }); -ariaTest('RIGHT ARROW changes focus and checks radio', exampleFile, 'key-down-right-arrow', async (t) => { - - let radios = await t.context.queryElements(t, ex.crustRadioSelector); - - // Right arrow moves focus to right - for (let index = 0; index < radios.length - 1; index++) { - await radios[index].sendKeys(Key.ARROW_RIGHT); - const newindex = index + 1; +ariaTest( + 'RIGHT ARROW changes focus and checks radio', + exampleFile, + 'key-down-right-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); + + // Right arrow moves focus to right + for (let index = 0; index < radios.length - 1; index++) { + await radios[index].sendKeys(Key.ARROW_RIGHT); + const newindex = index + 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newindex), + 'Focus should be on radio at index ' + + newindex + + ' after ARROW_RIGHT to radio' + + ' at index ' + + index + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_RIGHT to radio' + + ' at index ' + + index + ); + } + // Right arrow should move focus from last item to first + await radios[radios.length - 1].sendKeys(Key.ARROW_RIGHT); t.true( - await checkFocus(t, ex.crustRadioSelector, newindex), - 'Focus should be on radio at index ' + newindex + ' after ARROW_RIGHT to radio' + - ' at index ' + index + await checkFocus(t, ex.crustRadioSelector, 0), + 'Focus should be on radio at index 0 after ARROW_RIGHT to radio at index ' + + (radios.length - 1) ); t.is( - await radios[newindex].getAttribute('aria-checked'), + await radios[0].getAttribute('aria-checked'), 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_RIGHT to radio' + - ' at index ' + index + 'Radio at index 0 should be checked after ARROW_RIGHT to radio at index ' + + (radios.length - 1) ); } +); + +ariaTest( + 'DOWN ARROW changes focus and checks radio', + exampleFile, + 'key-down-right-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); + + // Down arrow moves focus to down + for (let index = 0; index < radios.length - 1; index++) { + await radios[index].sendKeys(Key.ARROW_DOWN); + const newindex = index + 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newindex), + 'Focus should be on radio at index ' + + newindex + + ' after ARROW_DOWN to radio' + + ' at index ' + + index + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_DOWN to radio' + + ' at index ' + + index + ); + } - // Right arrow should move focus from last item to first - await radios[radios.length - 1].sendKeys(Key.ARROW_RIGHT); - t.true( - await checkFocus(t, ex.crustRadioSelector, 0), - 'Focus should be on radio at index 0 after ARROW_RIGHT to radio at index ' + (radios.length - 1) - ); - t.is( - await radios[0].getAttribute('aria-checked'), - 'true', - 'Radio at index 0 should be checked after ARROW_RIGHT to radio at index ' + (radios.length - 1) - ); -}); - -ariaTest('DOWN ARROW changes focus and checks radio', exampleFile, 'key-down-right-arrow', async (t) => { - - let radios = await t.context.queryElements(t, ex.crustRadioSelector); - - // Down arrow moves focus to down - for (let index = 0; index < radios.length - 1; index++) { - await radios[index].sendKeys(Key.ARROW_DOWN); - const newindex = index + 1; - + // Down arrow should move focus from last item to first + await radios[radios.length - 1].sendKeys(Key.ARROW_DOWN); t.true( - await checkFocus(t, ex.crustRadioSelector, newindex), - 'Focus should be on radio at index ' + newindex + ' after ARROW_DOWN to radio' + - ' at index ' + index + await checkFocus(t, ex.crustRadioSelector, 0), + 'Focus should be on radio at index 0 after ARROW_DOWN to radio at index ' + + (radios.length - 1) ); t.is( - await radios[newindex].getAttribute('aria-checked'), + await radios[0].getAttribute('aria-checked'), 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_DOWN to radio' + - ' at index ' + index + 'Radio at index 0 should be checked after ARROW_DOWN to radio at index ' + + (radios.length - 1) ); } +); - // Down arrow should move focus from last item to first - await radios[radios.length - 1].sendKeys(Key.ARROW_DOWN); - t.true( - await checkFocus(t, ex.crustRadioSelector, 0), - 'Focus should be on radio at index 0 after ARROW_DOWN to radio at index ' + (radios.length - 1) - ); - t.is( - await radios[0].getAttribute('aria-checked'), - 'true', - 'Radio at index 0 should be checked after ARROW_DOWN to radio at index ' + (radios.length - 1) - ); -}); - -ariaTest('LEFT ARROW changes focus and checks radio', exampleFile, 'key-up-left-arrow', async (t) => { - - let radios = await t.context.queryElements(t, ex.crustRadioSelector); +ariaTest( + 'LEFT ARROW changes focus and checks radio', + exampleFile, + 'key-up-left-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); - // Left arrow should move focus from first item to last, then progressively left - await radios[0].sendKeys(Key.ARROW_LEFT); - - t.true( - await checkFocus(t, ex.crustRadioSelector, radios.length - 1), - 'Focus should be on radio at index ' + (radios.length - 1) + ' after ARROW_LEFT to radio at index 0' - ); - t.is( - await radios[radios.length - 1].getAttribute('aria-checked'), - 'true', - 'Radio at index ' + (radios.length - 1) + ' should be checked after ARROW_LEFT to radio at index 0' - ); - - - for (let index = radios.length - 1; index > 0 ; index--) { - await radios[index].sendKeys(Key.ARROW_LEFT); - const newindex = index - 1; + // Left arrow should move focus from first item to last, then progressively left + await radios[0].sendKeys(Key.ARROW_LEFT); t.true( - await checkFocus(t, ex.crustRadioSelector, newindex), - 'Focus should be on radio at index ' + newindex + ' after ARROW_LEFT to radio' + - ' at index ' + index + await checkFocus(t, ex.crustRadioSelector, radios.length - 1), + 'Focus should be on radio at index ' + + (radios.length - 1) + + ' after ARROW_LEFT to radio at index 0' ); t.is( - await radios[newindex].getAttribute('aria-checked'), + await radios[radios.length - 1].getAttribute('aria-checked'), 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_LEFT to radio' + - ' at index ' + index + 'Radio at index ' + + (radios.length - 1) + + ' should be checked after ARROW_LEFT to radio at index 0' ); - } -}); - -ariaTest('UP ARROW changes focus and checks radio', exampleFile, 'key-up-left-arrow', async (t) => { - - let radios = await t.context.queryElements(t, ex.crustRadioSelector); - - // Up arrow should move focus from first item to last, then progressively up - await radios[0].sendKeys(Key.ARROW_UP); - t.true( - await checkFocus(t, ex.crustRadioSelector, radios.length - 1), - 'Focus should be on radio at index ' + (radios.length - 1) + ' after ARROW_UP to radio at index 0' - ); - t.is( - await radios[radios.length - 1].getAttribute('aria-checked'), - 'true', - 'Radio at index ' + (radios.length - 1) + ' should be checked after ARROW_UP to radio at index 0' - ); + for (let index = radios.length - 1; index > 0; index--) { + await radios[index].sendKeys(Key.ARROW_LEFT); + const newindex = index - 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newindex), + 'Focus should be on radio at index ' + + newindex + + ' after ARROW_LEFT to radio' + + ' at index ' + + index + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_LEFT to radio' + + ' at index ' + + index + ); + } + } +); +ariaTest( + 'UP ARROW changes focus and checks radio', + exampleFile, + 'key-up-left-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); - for (let index = radios.length - 1; index > 0 ; index--) { - await radios[index].sendKeys(Key.ARROW_UP); - const newindex = index - 1; + // Up arrow should move focus from first item to last, then progressively up + await radios[0].sendKeys(Key.ARROW_UP); t.true( - await checkFocus(t, ex.crustRadioSelector, newindex), - 'Focus should be on radio at index ' + newindex + ' after ARROW_UP to radio' + - ' at index ' + index + await checkFocus(t, ex.crustRadioSelector, radios.length - 1), + 'Focus should be on radio at index ' + + (radios.length - 1) + + ' after ARROW_UP to radio at index 0' ); t.is( - await radios[newindex].getAttribute('aria-checked'), + await radios[radios.length - 1].getAttribute('aria-checked'), 'true', - 'Radio at index ' + newindex + ' should be checked after ARROW_UP to radio' + - ' at index ' + index + 'Radio at index ' + + (radios.length - 1) + + ' should be checked after ARROW_UP to radio at index 0' ); + + for (let index = radios.length - 1; index > 0; index--) { + await radios[index].sendKeys(Key.ARROW_UP); + const newindex = index - 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newindex), + 'Focus should be on radio at index ' + + newindex + + ' after ARROW_UP to radio' + + ' at index ' + + index + ); + t.is( + await radios[newindex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newindex + + ' should be checked after ARROW_UP to radio' + + ' at index ' + + index + ); + } } -}); +); diff --git a/test/tests/slider_multithumb.js b/test/tests/slider_multithumb.js index 2b6d74d249..71359a806e 100644 --- a/test/tests/slider_multithumb.js +++ b/test/tests/slider_multithumb.js @@ -10,22 +10,18 @@ const exampleFile = 'slider/multithumb-slider.html'; const ex = { sliderSelector: '#ex1 [role="slider"]', - hotelSliderSelector: '#ex1 div.aria-widget-slider:nth-of-type(1) [role="slider"]', - flightSliderSelector: '#ex1 div.aria-widget-slider:nth-of-type(2) [role="slider"]', + hotelSliderSelector: + '#ex1 div.aria-widget-slider:nth-of-type(1) [role="slider"]', + flightSliderSelector: + '#ex1 div.aria-widget-slider:nth-of-type(2) [role="slider"]', hotelMin: '0', hotelMax: '400', flightMin: '0', flightMax: '1000', - hotelDefaultVals: [ - '100', - '250' - ], - flightDefaultVals: [ - '100', - '250' - ], + hotelDefaultVals: ['100', '250'], + flightDefaultVals: ['100', '250'], hotelLabelSelector: '#ex1 div.aria-widget-slider:nth-of-type(1) .rail-label', - flightLabelSelector: '#ex1 div.aria-widget-slider:nth-of-type(2) .rail-label' + flightLabelSelector: '#ex1 div.aria-widget-slider:nth-of-type(2) .rail-label', }; const verifyAllValues = async function ( @@ -50,843 +46,984 @@ const verifyAllValues = async function ( attribute2 + ' on second slider: ' + message ); - t.is( - await label.getText(), - '$' + value, - 'value in label after: ' + message - ); -}; - -// Attributes - -ariaTest('role="slider" on div element', exampleFile, 'slider-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'slider', '4', 'img'); -}); - -ariaTest('"tabindex" set to "0" on sliders', exampleFile, 'tabindex', async (t) => { - await assertAttributeValues(t, ex.sliderSelector, 'tabindex', '0'); -}); - -ariaTest('"aria-valuemax" set on sliders', exampleFile, 'aria-valuemax', async (t) => { - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - - t.is( - await hotelSliders[0].getAttribute('aria-valuemax'), - ex.hotelDefaultVals[1], - 'Value of "aria-valuemax" for first hotel slider on page load should be: ' + ex.hotelDefaultVals[1] - ); - t.is( - await hotelSliders[1].getAttribute('aria-valuemax'), - ex.hotelMax, - 'Value of "aria-valuemax" for second hotel slider on page load should be: ' + ex.hotelMax - ); - t.is( - await flightSliders[0].getAttribute('aria-valuemax'), - ex.flightDefaultVals[1], - 'Value of "aria-valuemax" for first flight slider on page load should be: ' + ex.flightDefaultVals[1] - ); - - t.is( - await flightSliders[1].getAttribute('aria-valuemax'), - ex.flightMax, - 'Value of "aria-valuemax" for second flight slider on page load should be: ' + ex.flightMax - ); -}); - -ariaTest('"aria-valuemin" set to "0" on sliders', exampleFile, 'aria-valuemin', async (t) => { - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - - t.is( - await hotelSliders[0].getAttribute('aria-valuemin'), - '0', - 'Value of "aria-valuemin" for first hotel slider on page load should be: "0"' - ); - t.is( - await hotelSliders[1].getAttribute('aria-valuemin'), - ex.hotelDefaultVals[0], - 'Value of "aria-valuemin" for second hotel slider on page load should be: ' + ex.hotelDefaultVals[0] - ); - t.is( - await flightSliders[0].getAttribute('aria-valuemin'), - '0', - 'Value of "aria-valuemin" for first flight slider on page load should be: "0"' - ); - t.is( - await flightSliders[1].getAttribute('aria-valuemin'), - ex.flightDefaultVals[0], - 'Value of "aria-valuemin" for second flight slider on page load should be: ' + ex.flightDefaultVals[0] - ); -}); - -ariaTest('"aria-valuenow" reflects slider value', exampleFile, 'aria-valuenow', async (t) => { - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - - t.is( - await hotelSliders[0].getAttribute('aria-valuenow'), - ex.hotelDefaultVals[0], - 'Value of "aria-valuenow" for first hotel slider on page load should be: ' + ex.hotelDefaultVals[0] - ); - t.is( - await hotelSliders[1].getAttribute('aria-valuenow'), - ex.hotelDefaultVals[1], - 'Value of "aria-valuenow" for second hotel slider on page load should be: ' + ex.hotelDefaultVals[1] - ); - t.is( - await flightSliders[0].getAttribute('aria-valuenow'), - ex.flightDefaultVals[0], - 'Value of "aria-valuenow" for first flight slider on page load should be: ' + ex.flightDefaultVals[0] - ); - t.is( - await flightSliders[1].getAttribute('aria-valuenow'), - ex.flightDefaultVals[1], - 'Value of "aria-valuenow" for second flight slider on page load should be: ' + ex.flightDefaultVals[1] - ); -}); - -ariaTest('"aria-valuetext" reflects slider value', exampleFile, 'aria-valuetext', async (t) => { - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - - t.is( - await hotelSliders[0].getAttribute('aria-valuetext'), - '$' + ex.hotelDefaultVals[0], - 'Value of "aria-valuetext" for first hotel slider on page load should be: $' + ex.hotelDefaultVals[0] - ); - t.is( - await hotelSliders[1].getAttribute('aria-valuetext'), - '$' + ex.hotelDefaultVals[1], - 'Value of "aria-valuetext" for second hotel slider on page load should be: $' + ex.hotelDefaultVals[1] - ); - t.is( - await flightSliders[0].getAttribute('aria-valuetext'), - '$' + ex.flightDefaultVals[0], - 'Value of "aria-valuetext" for first flight slider on page load should be: $' + ex.flightDefaultVals[0] - ); - t.is( - await flightSliders[1].getAttribute('aria-valuetext'), - '$' + ex.flightDefaultVals[1], - 'Value of "aria-valuetext" for second flight slider on page load should be: $' + ex.flightDefaultVals[1] - ); -}); - - -ariaTest('"aria-label" set on sliders', exampleFile, 'aria-label', async (t) => { - await assertAriaLabelExists(t, ex.sliderSelector); -}); - -// Keys - -ariaTest('Right arrow increases slider value by 1', exampleFile, 'key-right-arrow', async (t) => { - - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); - - // Send 1 key to lower hotel slider - await hotelSliders[0].sendKeys(Key.ARROW_RIGHT); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[0]) + 1).toString(), - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after one ARROW RIGHT to lower hotel slider' - ); - - await hotelSliders[0].sendKeys(Key.END, Key.ARROW_RIGHT); - - await verifyAllValues( - t, - ex.hotelDefaultVals[1], - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after END then one ARROW RIGHT to lower hotel slider' - ); - - // Send 1 key to lower upper slider - await hotelSliders[1].sendKeys(Key.ARROW_RIGHT); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[1]) + 1).toString(), - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after one ARROW RIGHT to lower hotel slider' - ); - - await hotelSliders[1].sendKeys(Key.END, Key.ARROW_RIGHT); - - await verifyAllValues( - t, - ex.hotelMax, - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after END then one ARROW RIGHT to upper hotel slider' - ); - - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); - - // Send 1 key to lower flight slider - await flightSliders[0].sendKeys(Key.ARROW_RIGHT); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[0]) + 1).toString(), - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after one ARROW RIGHT to lower flight slider' - ); - - await flightSliders[0].sendKeys(Key.END, Key.ARROW_RIGHT); - - await verifyAllValues( - t, - ex.flightDefaultVals[1], - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after END then one ARROW RIGHT to lower flight slider' - ); - - // Send 1 key to lower upper slider - await flightSliders[1].sendKeys(Key.ARROW_RIGHT); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[1]) + 1).toString(), - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after one ARROW RIGHT to lower flight slider' - ); - - await flightSliders[1].sendKeys(Key.END, Key.ARROW_RIGHT); - - await verifyAllValues( - t, - ex.flightMax, - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after END then one ARROW RIGHT to upper flight slider' - ); -}); - -ariaTest('Up arrow increases slider value by 1', exampleFile, 'key-up-arrow', async (t) => { - - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); - - // Send 1 key to lower hotel slider - await hotelSliders[0].sendKeys(Key.ARROW_UP); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[0]) + 1).toString(), - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after one ARROW UP to lower hotel slider' - ); - - await hotelSliders[0].sendKeys(Key.END, Key.ARROW_UP); - - await verifyAllValues( - t, - ex.hotelDefaultVals[1], - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after END then one ARROW UP to lower hotel slider' - ); - - // Send 1 key to lower upper slider - await hotelSliders[1].sendKeys(Key.ARROW_UP); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[1]) + 1).toString(), - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after one ARROW UP to lower hotel slider' - ); - - await hotelSliders[1].sendKeys(Key.END, Key.ARROW_UP); - - await verifyAllValues( - t, - ex.hotelMax, - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after END then one ARROW UP to upper hotel slider' - ); - - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); - - // Send 1 key to lower flight slider - await flightSliders[0].sendKeys(Key.ARROW_UP); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[0]) + 1).toString(), - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after one ARROW UP to lower flight slider' - ); - - await flightSliders[0].sendKeys(Key.END, Key.ARROW_UP); - - await verifyAllValues( - t, - ex.flightDefaultVals[1], - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after END then one ARROW UP to lower flight slider' - ); - - // Send 1 key to lower upper slider - await flightSliders[1].sendKeys(Key.ARROW_UP); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[1]) + 1).toString(), - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after one ARROW UP to lower flight slider' - ); - - await flightSliders[1].sendKeys(Key.END, Key.ARROW_UP); - - await verifyAllValues( - t, - ex.flightMax, - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after END then one ARROW UP to upper flight slider' - ); -}); - - -ariaTest('Page up increases slider value by 10', exampleFile, 'key-page-up', async (t) => { - - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); - - // Send 1 key to lower hotel slider - await hotelSliders[0].sendKeys(Key.PAGE_UP); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[0]) + 10).toString(), - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after one PAGE UP to lower hotel slider' - ); - - await hotelSliders[0].sendKeys(Key.END, Key.PAGE_UP); - - await verifyAllValues( - t, - ex.hotelDefaultVals[1], - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after END then one PAGE UP to lower hotel slider' - ); - - // Send 1 key to lower upper slider - await hotelSliders[1].sendKeys(Key.PAGE_UP); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[1]) + 10).toString(), - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after one PAGE UP to upper hotel slider' - ); - - await hotelSliders[1].sendKeys(Key.END, Key.PAGE_UP); - - await verifyAllValues( - t, - ex.hotelMax, - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after END then one PAGE UP to upper hotel slider' - ); - - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); - - // Send 1 key to lower flight slider - await flightSliders[0].sendKeys(Key.PAGE_UP); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[0]) + 10).toString(), - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after one PAGE UP to lower flight slider' - ); - - await flightSliders[0].sendKeys(Key.END, Key.PAGE_UP); - - await verifyAllValues( - t, - ex.flightDefaultVals[1], - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after END then one PAGE UP to lower flight slider' - ); - - // Send 1 key to lower upper slider - await flightSliders[1].sendKeys(Key.PAGE_UP); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[1]) + 10).toString(), - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after one PAGE UP to upper flight slider' - ); - - await flightSliders[1].sendKeys(Key.END, Key.PAGE_UP); - - await verifyAllValues( - t, - ex.flightMax, - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after END then one PAGE UP to upper flight slider' - ); -}); - - -ariaTest('left arrow decreases slider value by 1', exampleFile, 'key-left-arrow', async (t) => { - - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); - - // Send 1 key to lower hotel slider - await hotelSliders[0].sendKeys(Key.ARROW_LEFT); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[0]) - 1).toString(), - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after one ARROW LEFT to lower hotel slider' - ); - - await hotelSliders[0].sendKeys(Key.HOME, Key.ARROW_LEFT); - - await verifyAllValues( - t, - ex.hotelMin, - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after HOME then one ARROW LEFT to lower hotel slider' - ); - - // Send 1 key to lower upper slider - await hotelSliders[1].sendKeys(Key.ARROW_LEFT); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[1]) - 1).toString(), - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after one ARROW LEFT to upper hotel slider' - ); - - await hotelSliders[1].sendKeys(Key.HOME, Key.ARROW_LEFT); - - await verifyAllValues( - t, - ex.hotelMin, - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after HOME then one ARROW LEFT to upper hotel slider' - ); - - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); - - // Send 1 key to lower flight slider - await flightSliders[0].sendKeys(Key.ARROW_LEFT); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[0]) - 1).toString(), - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after one ARROW LEFT to lower flight slider' - ); - - await flightSliders[0].sendKeys(Key.HOME, Key.ARROW_LEFT); - - await verifyAllValues( - t, - ex.flightMin, - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after HOME then one ARROW LEFT to lower flight slider' - ); - - // Send 1 key to lower upper slider - await flightSliders[1].sendKeys(Key.ARROW_LEFT); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[1]) - 1).toString(), - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after one ARROW LEFT to upper flight slider' - ); - - await flightSliders[1].sendKeys(Key.HOME, Key.ARROW_LEFT); - - await verifyAllValues( - t, - ex.flightMin, - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after HOME then one ARROW LEFT to upper flight slider' - ); -}); - -ariaTest('down arrow decreases slider value by 1', exampleFile, 'key-down-arrow', async (t) => { - - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); - - // Send 1 key to lower hotel slider - await hotelSliders[0].sendKeys(Key.ARROW_DOWN); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[0]) - 1).toString(), - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after one ARROW DOWN to lower hotel slider' - ); - - await hotelSliders[0].sendKeys(Key.HOME, Key.ARROW_DOWN); - - await verifyAllValues( - t, - ex.hotelMin, - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after HOME then one ARROW DOWN to lower hotel slider' - ); - - // Send 1 key to lower upper slider - await hotelSliders[1].sendKeys(Key.ARROW_DOWN); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[1]) - 1).toString(), - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after one ARROW DOWN to upper hotel slider' - ); - - await hotelSliders[1].sendKeys(Key.HOME, Key.ARROW_DOWN); - - await verifyAllValues( - t, - ex.hotelMin, - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after HOME then one ARROW DOWN to upper hotel slider' - ); - - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); - - // Send 1 key to lower flight slider - await flightSliders[0].sendKeys(Key.ARROW_DOWN); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[0]) - 1).toString(), - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after one ARROW DOWN to lower flight slider' - ); - - await flightSliders[0].sendKeys(Key.HOME, Key.ARROW_DOWN); - - await verifyAllValues( - t, - ex.flightMin, - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after HOME then one ARROW DOWN to lower flight slider' - ); - - // Send 1 key to lower upper slider - await flightSliders[1].sendKeys(Key.ARROW_DOWN); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[1]) - 1).toString(), - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after one ARROW DOWN to upper flight slider' - ); - - await flightSliders[1].sendKeys(Key.HOME, Key.ARROW_DOWN); - - await verifyAllValues( - t, - ex.flightMin, - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after HOME then one ARROW DOWN to upper flight slider' - ); -}); - - -ariaTest('page down decreases slider value by 10', exampleFile, 'key-page-down', async (t) => { - - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); - const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); - - // Send 1 key to lower hotel slider - await hotelSliders[0].sendKeys(Key.PAGE_DOWN); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[0]) - 10).toString(), - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after one PAGE DOWN to lower hotel slider' - ); - - await hotelSliders[0].sendKeys(Key.HOME, Key.PAGE_DOWN); - - await verifyAllValues( - t, - ex.hotelMin, - hotelSliders[0], - 'aria-valuenow', - hotelSliders[1], - 'aria-valuemin', - hotelLabels[0], - 'after HOME then one PAGE DOWN to lower hotel slider' - ); - - // Send 1 key to lower upper slider - await hotelSliders[1].sendKeys(Key.PAGE_DOWN); - - await verifyAllValues( - t, - (parseInt(ex.hotelDefaultVals[1]) - 10).toString(), - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after one PAGE DOWN to upper hotel slider' - ); - - await hotelSliders[1].sendKeys(Key.HOME, Key.PAGE_DOWN); - - await verifyAllValues( - t, - ex.hotelMin, - hotelSliders[0], - 'aria-valuemax', - hotelSliders[1], - 'aria-valuenow', - hotelLabels[1], - 'after HOME then one PAGE DOWN to upper hotel slider' - ); - - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); - const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); - - // Send 1 key to lower flight slider - await flightSliders[0].sendKeys(Key.PAGE_DOWN); - - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[0]) - 10).toString(), - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after one PAGE DOWN to lower flight slider' - ); - - await flightSliders[0].sendKeys(Key.HOME, Key.PAGE_DOWN); - - await verifyAllValues( - t, - ex.flightMin, - flightSliders[0], - 'aria-valuenow', - flightSliders[1], - 'aria-valuemin', - flightLabels[0], - 'after HOME then one PAGE DOWN to lower flight slider' - ); - - // Send 1 key to lower upper slider - await flightSliders[1].sendKeys(Key.PAGE_DOWN); + t.is(await label.getText(), '$' + value, 'value in label after: ' + message); +}; - await verifyAllValues( - t, - (parseInt(ex.flightDefaultVals[1]) - 10).toString(), - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after one PAGE DOWN to lower flight slider' - ); +// Attributes - await flightSliders[1].sendKeys(Key.HOME, Key.PAGE_DOWN); +ariaTest( + 'role="slider" on div element', + exampleFile, + 'slider-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'slider', '4', 'img'); + } +); + +ariaTest( + '"tabindex" set to "0" on sliders', + exampleFile, + 'tabindex', + async (t) => { + await assertAttributeValues(t, ex.sliderSelector, 'tabindex', '0'); + } +); + +ariaTest( + '"aria-valuemax" set on sliders', + exampleFile, + 'aria-valuemax', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + + t.is( + await hotelSliders[0].getAttribute('aria-valuemax'), + ex.hotelDefaultVals[1], + 'Value of "aria-valuemax" for first hotel slider on page load should be: ' + + ex.hotelDefaultVals[1] + ); + t.is( + await hotelSliders[1].getAttribute('aria-valuemax'), + ex.hotelMax, + 'Value of "aria-valuemax" for second hotel slider on page load should be: ' + + ex.hotelMax + ); + t.is( + await flightSliders[0].getAttribute('aria-valuemax'), + ex.flightDefaultVals[1], + 'Value of "aria-valuemax" for first flight slider on page load should be: ' + + ex.flightDefaultVals[1] + ); + + t.is( + await flightSliders[1].getAttribute('aria-valuemax'), + ex.flightMax, + 'Value of "aria-valuemax" for second flight slider on page load should be: ' + + ex.flightMax + ); + } +); + +ariaTest( + '"aria-valuemin" set to "0" on sliders', + exampleFile, + 'aria-valuemin', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + + t.is( + await hotelSliders[0].getAttribute('aria-valuemin'), + '0', + 'Value of "aria-valuemin" for first hotel slider on page load should be: "0"' + ); + t.is( + await hotelSliders[1].getAttribute('aria-valuemin'), + ex.hotelDefaultVals[0], + 'Value of "aria-valuemin" for second hotel slider on page load should be: ' + + ex.hotelDefaultVals[0] + ); + t.is( + await flightSliders[0].getAttribute('aria-valuemin'), + '0', + 'Value of "aria-valuemin" for first flight slider on page load should be: "0"' + ); + t.is( + await flightSliders[1].getAttribute('aria-valuemin'), + ex.flightDefaultVals[0], + 'Value of "aria-valuemin" for second flight slider on page load should be: ' + + ex.flightDefaultVals[0] + ); + } +); + +ariaTest( + '"aria-valuenow" reflects slider value', + exampleFile, + 'aria-valuenow', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + + t.is( + await hotelSliders[0].getAttribute('aria-valuenow'), + ex.hotelDefaultVals[0], + 'Value of "aria-valuenow" for first hotel slider on page load should be: ' + + ex.hotelDefaultVals[0] + ); + t.is( + await hotelSliders[1].getAttribute('aria-valuenow'), + ex.hotelDefaultVals[1], + 'Value of "aria-valuenow" for second hotel slider on page load should be: ' + + ex.hotelDefaultVals[1] + ); + t.is( + await flightSliders[0].getAttribute('aria-valuenow'), + ex.flightDefaultVals[0], + 'Value of "aria-valuenow" for first flight slider on page load should be: ' + + ex.flightDefaultVals[0] + ); + t.is( + await flightSliders[1].getAttribute('aria-valuenow'), + ex.flightDefaultVals[1], + 'Value of "aria-valuenow" for second flight slider on page load should be: ' + + ex.flightDefaultVals[1] + ); + } +); + +ariaTest( + '"aria-valuetext" reflects slider value', + exampleFile, + 'aria-valuetext', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + + t.is( + await hotelSliders[0].getAttribute('aria-valuetext'), + '$' + ex.hotelDefaultVals[0], + 'Value of "aria-valuetext" for first hotel slider on page load should be: $' + + ex.hotelDefaultVals[0] + ); + t.is( + await hotelSliders[1].getAttribute('aria-valuetext'), + '$' + ex.hotelDefaultVals[1], + 'Value of "aria-valuetext" for second hotel slider on page load should be: $' + + ex.hotelDefaultVals[1] + ); + t.is( + await flightSliders[0].getAttribute('aria-valuetext'), + '$' + ex.flightDefaultVals[0], + 'Value of "aria-valuetext" for first flight slider on page load should be: $' + + ex.flightDefaultVals[0] + ); + t.is( + await flightSliders[1].getAttribute('aria-valuetext'), + '$' + ex.flightDefaultVals[1], + 'Value of "aria-valuetext" for second flight slider on page load should be: $' + + ex.flightDefaultVals[1] + ); + } +); + +ariaTest( + '"aria-label" set on sliders', + exampleFile, + 'aria-label', + async (t) => { + await assertAriaLabelExists(t, ex.sliderSelector); + } +); - await verifyAllValues( - t, - ex.flightMin, - flightSliders[0], - 'aria-valuemax', - flightSliders[1], - 'aria-valuenow', - flightLabels[1], - 'after HOME then one PAGE DOWN to upper flight slider' - ); -}); +// Keys +ariaTest( + 'Right arrow increases slider value by 1', + exampleFile, + 'key-right-arrow', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); + + // Send 1 key to lower hotel slider + await hotelSliders[0].sendKeys(Key.ARROW_RIGHT); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[0]) + 1).toString(), + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after one ARROW RIGHT to lower hotel slider' + ); + + await hotelSliders[0].sendKeys(Key.END, Key.ARROW_RIGHT); + + await verifyAllValues( + t, + ex.hotelDefaultVals[1], + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after END then one ARROW RIGHT to lower hotel slider' + ); + + // Send 1 key to lower upper slider + await hotelSliders[1].sendKeys(Key.ARROW_RIGHT); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[1]) + 1).toString(), + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after one ARROW RIGHT to lower hotel slider' + ); + + await hotelSliders[1].sendKeys(Key.END, Key.ARROW_RIGHT); + + await verifyAllValues( + t, + ex.hotelMax, + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after END then one ARROW RIGHT to upper hotel slider' + ); + + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + const flightLabels = await t.context.queryElements( + t, + ex.flightLabelSelector + ); + + // Send 1 key to lower flight slider + await flightSliders[0].sendKeys(Key.ARROW_RIGHT); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[0]) + 1).toString(), + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after one ARROW RIGHT to lower flight slider' + ); + + await flightSliders[0].sendKeys(Key.END, Key.ARROW_RIGHT); + + await verifyAllValues( + t, + ex.flightDefaultVals[1], + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after END then one ARROW RIGHT to lower flight slider' + ); + + // Send 1 key to lower upper slider + await flightSliders[1].sendKeys(Key.ARROW_RIGHT); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[1]) + 1).toString(), + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after one ARROW RIGHT to lower flight slider' + ); + + await flightSliders[1].sendKeys(Key.END, Key.ARROW_RIGHT); + + await verifyAllValues( + t, + ex.flightMax, + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after END then one ARROW RIGHT to upper flight slider' + ); + } +); + +ariaTest( + 'Up arrow increases slider value by 1', + exampleFile, + 'key-up-arrow', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); + + // Send 1 key to lower hotel slider + await hotelSliders[0].sendKeys(Key.ARROW_UP); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[0]) + 1).toString(), + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after one ARROW UP to lower hotel slider' + ); + + await hotelSliders[0].sendKeys(Key.END, Key.ARROW_UP); + + await verifyAllValues( + t, + ex.hotelDefaultVals[1], + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after END then one ARROW UP to lower hotel slider' + ); + + // Send 1 key to lower upper slider + await hotelSliders[1].sendKeys(Key.ARROW_UP); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[1]) + 1).toString(), + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after one ARROW UP to lower hotel slider' + ); + + await hotelSliders[1].sendKeys(Key.END, Key.ARROW_UP); + + await verifyAllValues( + t, + ex.hotelMax, + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after END then one ARROW UP to upper hotel slider' + ); + + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + const flightLabels = await t.context.queryElements( + t, + ex.flightLabelSelector + ); + + // Send 1 key to lower flight slider + await flightSliders[0].sendKeys(Key.ARROW_UP); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[0]) + 1).toString(), + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after one ARROW UP to lower flight slider' + ); + + await flightSliders[0].sendKeys(Key.END, Key.ARROW_UP); + + await verifyAllValues( + t, + ex.flightDefaultVals[1], + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after END then one ARROW UP to lower flight slider' + ); + + // Send 1 key to lower upper slider + await flightSliders[1].sendKeys(Key.ARROW_UP); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[1]) + 1).toString(), + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after one ARROW UP to lower flight slider' + ); + + await flightSliders[1].sendKeys(Key.END, Key.ARROW_UP); + + await verifyAllValues( + t, + ex.flightMax, + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after END then one ARROW UP to upper flight slider' + ); + } +); + +ariaTest( + 'Page up increases slider value by 10', + exampleFile, + 'key-page-up', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); + + // Send 1 key to lower hotel slider + await hotelSliders[0].sendKeys(Key.PAGE_UP); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[0]) + 10).toString(), + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after one PAGE UP to lower hotel slider' + ); + + await hotelSliders[0].sendKeys(Key.END, Key.PAGE_UP); + + await verifyAllValues( + t, + ex.hotelDefaultVals[1], + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after END then one PAGE UP to lower hotel slider' + ); + + // Send 1 key to lower upper slider + await hotelSliders[1].sendKeys(Key.PAGE_UP); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[1]) + 10).toString(), + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after one PAGE UP to upper hotel slider' + ); + + await hotelSliders[1].sendKeys(Key.END, Key.PAGE_UP); + + await verifyAllValues( + t, + ex.hotelMax, + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after END then one PAGE UP to upper hotel slider' + ); + + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + const flightLabels = await t.context.queryElements( + t, + ex.flightLabelSelector + ); + + // Send 1 key to lower flight slider + await flightSliders[0].sendKeys(Key.PAGE_UP); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[0]) + 10).toString(), + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after one PAGE UP to lower flight slider' + ); + + await flightSliders[0].sendKeys(Key.END, Key.PAGE_UP); + + await verifyAllValues( + t, + ex.flightDefaultVals[1], + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after END then one PAGE UP to lower flight slider' + ); + + // Send 1 key to lower upper slider + await flightSliders[1].sendKeys(Key.PAGE_UP); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[1]) + 10).toString(), + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after one PAGE UP to upper flight slider' + ); + + await flightSliders[1].sendKeys(Key.END, Key.PAGE_UP); + + await verifyAllValues( + t, + ex.flightMax, + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after END then one PAGE UP to upper flight slider' + ); + } +); + +ariaTest( + 'left arrow decreases slider value by 1', + exampleFile, + 'key-left-arrow', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); + + // Send 1 key to lower hotel slider + await hotelSliders[0].sendKeys(Key.ARROW_LEFT); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[0]) - 1).toString(), + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after one ARROW LEFT to lower hotel slider' + ); + + await hotelSliders[0].sendKeys(Key.HOME, Key.ARROW_LEFT); + + await verifyAllValues( + t, + ex.hotelMin, + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after HOME then one ARROW LEFT to lower hotel slider' + ); + + // Send 1 key to lower upper slider + await hotelSliders[1].sendKeys(Key.ARROW_LEFT); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[1]) - 1).toString(), + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after one ARROW LEFT to upper hotel slider' + ); + + await hotelSliders[1].sendKeys(Key.HOME, Key.ARROW_LEFT); + + await verifyAllValues( + t, + ex.hotelMin, + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after HOME then one ARROW LEFT to upper hotel slider' + ); + + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + const flightLabels = await t.context.queryElements( + t, + ex.flightLabelSelector + ); + + // Send 1 key to lower flight slider + await flightSliders[0].sendKeys(Key.ARROW_LEFT); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[0]) - 1).toString(), + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after one ARROW LEFT to lower flight slider' + ); + + await flightSliders[0].sendKeys(Key.HOME, Key.ARROW_LEFT); + + await verifyAllValues( + t, + ex.flightMin, + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after HOME then one ARROW LEFT to lower flight slider' + ); + + // Send 1 key to lower upper slider + await flightSliders[1].sendKeys(Key.ARROW_LEFT); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[1]) - 1).toString(), + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after one ARROW LEFT to upper flight slider' + ); + + await flightSliders[1].sendKeys(Key.HOME, Key.ARROW_LEFT); + + await verifyAllValues( + t, + ex.flightMin, + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after HOME then one ARROW LEFT to upper flight slider' + ); + } +); + +ariaTest( + 'down arrow decreases slider value by 1', + exampleFile, + 'key-down-arrow', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); + + // Send 1 key to lower hotel slider + await hotelSliders[0].sendKeys(Key.ARROW_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[0]) - 1).toString(), + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after one ARROW DOWN to lower hotel slider' + ); + + await hotelSliders[0].sendKeys(Key.HOME, Key.ARROW_DOWN); + + await verifyAllValues( + t, + ex.hotelMin, + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after HOME then one ARROW DOWN to lower hotel slider' + ); + + // Send 1 key to lower upper slider + await hotelSliders[1].sendKeys(Key.ARROW_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[1]) - 1).toString(), + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after one ARROW DOWN to upper hotel slider' + ); + + await hotelSliders[1].sendKeys(Key.HOME, Key.ARROW_DOWN); + + await verifyAllValues( + t, + ex.hotelMin, + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after HOME then one ARROW DOWN to upper hotel slider' + ); + + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + const flightLabels = await t.context.queryElements( + t, + ex.flightLabelSelector + ); + + // Send 1 key to lower flight slider + await flightSliders[0].sendKeys(Key.ARROW_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[0]) - 1).toString(), + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after one ARROW DOWN to lower flight slider' + ); + + await flightSliders[0].sendKeys(Key.HOME, Key.ARROW_DOWN); + + await verifyAllValues( + t, + ex.flightMin, + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after HOME then one ARROW DOWN to lower flight slider' + ); + + // Send 1 key to lower upper slider + await flightSliders[1].sendKeys(Key.ARROW_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[1]) - 1).toString(), + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after one ARROW DOWN to upper flight slider' + ); + + await flightSliders[1].sendKeys(Key.HOME, Key.ARROW_DOWN); + + await verifyAllValues( + t, + ex.flightMin, + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after HOME then one ARROW DOWN to upper flight slider' + ); + } +); + +ariaTest( + 'page down decreases slider value by 10', + exampleFile, + 'key-page-down', + async (t) => { + const hotelSliders = await t.context.queryElements( + t, + ex.hotelSliderSelector + ); + const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); + + // Send 1 key to lower hotel slider + await hotelSliders[0].sendKeys(Key.PAGE_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[0]) - 10).toString(), + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after one PAGE DOWN to lower hotel slider' + ); + + await hotelSliders[0].sendKeys(Key.HOME, Key.PAGE_DOWN); + + await verifyAllValues( + t, + ex.hotelMin, + hotelSliders[0], + 'aria-valuenow', + hotelSliders[1], + 'aria-valuemin', + hotelLabels[0], + 'after HOME then one PAGE DOWN to lower hotel slider' + ); + + // Send 1 key to lower upper slider + await hotelSliders[1].sendKeys(Key.PAGE_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.hotelDefaultVals[1]) - 10).toString(), + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after one PAGE DOWN to upper hotel slider' + ); + + await hotelSliders[1].sendKeys(Key.HOME, Key.PAGE_DOWN); + + await verifyAllValues( + t, + ex.hotelMin, + hotelSliders[0], + 'aria-valuemax', + hotelSliders[1], + 'aria-valuenow', + hotelLabels[1], + 'after HOME then one PAGE DOWN to upper hotel slider' + ); + + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); + const flightLabels = await t.context.queryElements( + t, + ex.flightLabelSelector + ); + + // Send 1 key to lower flight slider + await flightSliders[0].sendKeys(Key.PAGE_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[0]) - 10).toString(), + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after one PAGE DOWN to lower flight slider' + ); + + await flightSliders[0].sendKeys(Key.HOME, Key.PAGE_DOWN); + + await verifyAllValues( + t, + ex.flightMin, + flightSliders[0], + 'aria-valuenow', + flightSliders[1], + 'aria-valuemin', + flightLabels[0], + 'after HOME then one PAGE DOWN to lower flight slider' + ); + + // Send 1 key to lower upper slider + await flightSliders[1].sendKeys(Key.PAGE_DOWN); + + await verifyAllValues( + t, + (parseInt(ex.flightDefaultVals[1]) - 10).toString(), + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after one PAGE DOWN to lower flight slider' + ); + + await flightSliders[1].sendKeys(Key.HOME, Key.PAGE_DOWN); + + await verifyAllValues( + t, + ex.flightMin, + flightSliders[0], + 'aria-valuemax', + flightSliders[1], + 'aria-valuenow', + flightLabels[1], + 'after HOME then one PAGE DOWN to upper flight slider' + ); + } +); ariaTest('home sends value to minimum', exampleFile, 'key-home', async (t) => { - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); @@ -918,7 +1055,10 @@ ariaTest('home sends value to minimum', exampleFile, 'key-home', async (t) => { 'after one HOME to lower hotel slider' ); - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); // Send 1 key to upper flight slider @@ -950,9 +1090,7 @@ ariaTest('home sends value to minimum', exampleFile, 'key-home', async (t) => { ); }); - ariaTest('end sends value to minimum', exampleFile, 'key-end', async (t) => { - const hotelSliders = await t.context.queryElements(t, ex.hotelSliderSelector); const hotelLabels = await t.context.queryElements(t, ex.hotelLabelSelector); @@ -982,7 +1120,10 @@ ariaTest('end sends value to minimum', exampleFile, 'key-end', async (t) => { 'after one END to upper hotel slider' ); - const flightSliders = await t.context.queryElements(t, ex.flightSliderSelector); + const flightSliders = await t.context.queryElements( + t, + ex.flightSliderSelector + ); const flightLabels = await t.context.queryElements(t, ex.flightLabelSelector); await flightSliders[0].sendKeys(Key.END); diff --git a/test/tests/slider_slider-1.js b/test/tests/slider_slider-1.js index ef657a4b26..d328987685 100644 --- a/test/tests/slider_slider-1.js +++ b/test/tests/slider_slider-1.js @@ -12,7 +12,7 @@ const ex = { sliderSelector: '#ex1 [role="slider"]', hexTextInput: '#idColorValueHex', rgbTextInput: '#idColorValueRGB', - colorBox: '#idColorBox' + colorBox: '#idColorBox', }; const testDisplayMatchesValue = async function (t, rgbString) { @@ -27,23 +27,40 @@ const testDisplayMatchesValue = async function (t, rgbString) { .getCssValue('background-color'); if (rgbValue !== rgbString) { - return ex.rgbTextInput + ' was not update, value is ' + rgbValue + - ' but expected ' + rgbString; + return ( + ex.rgbTextInput + + ' was not update, value is ' + + rgbValue + + ' but expected ' + + rgbString + ); } if (boxColor !== 'rgb(' + rgbString + ')') { - return 'Box color was not update, background-color is ' + boxColor + - ' but expected ' + 'rgb(' + rgbString + ')'; + return ( + 'Box color was not update, background-color is ' + + boxColor + + ' but expected ' + + 'rgb(' + + rgbString + + ')' + ); } - const rbgFromHexString = hexValue.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i) - .slice(1,4) - .map(x => parseInt(x, 16)) + const rbgFromHexString = hexValue + .match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i) + .slice(1, 4) + .map((x) => parseInt(x, 16)) .join(', '); if (rbgFromHexString !== rgbString) { - return ex.hexTextInput + ' was not update, value is ' + rbgFromHexString + - ' but expected ' + rgbString; + return ( + ex.hexTextInput + + ' was not update, value is ' + + rbgFromHexString + + ' but expected ' + + rgbString + ); } return true; @@ -59,683 +76,744 @@ const sendAllSlidersToEnd = async function (t) { // Attributes -ariaTest('role="slider" on div element', exampleFile, 'slider-role', async (t) => { +ariaTest( + 'role="slider" on div element', + exampleFile, + 'slider-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'slider', '3', 'div'); -}); + } +); -ariaTest('"tabindex" set to "0" on sliders', exampleFile, 'tabindex', async (t) => { +ariaTest( + '"tabindex" set to "0" on sliders', + exampleFile, + 'tabindex', + async (t) => { await assertAttributeValues(t, ex.sliderSelector, 'tabindex', '0'); -}); + } +); -ariaTest('"aria-valuemax" set to "255" on sliders', exampleFile, 'aria-valuemax', async (t) => { +ariaTest( + '"aria-valuemax" set to "255" on sliders', + exampleFile, + 'aria-valuemax', + async (t) => { await assertAttributeValues(t, ex.sliderSelector, 'aria-valuemax', '255'); -}); + } +); -ariaTest('"aria-valuemin" set to "0" on sliders', exampleFile, 'aria-valuemin', async (t) => { +ariaTest( + '"aria-valuemin" set to "0" on sliders', + exampleFile, + 'aria-valuemin', + async (t) => { await assertAttributeValues(t, ex.sliderSelector, 'aria-valuemin', '0'); -}); + } +); -ariaTest('"aria-valuenow" reflects slider value', exampleFile, 'aria-valuenow', async (t) => { +ariaTest( + '"aria-valuenow" reflects slider value', + exampleFile, + 'aria-valuenow', + async (t) => { await assertAttributeValues(t, ex.sliderSelector, 'aria-valuenow', '0'); -}); + } +); -ariaTest('"aria-labelledby" set on sliders', exampleFile, 'aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" set on sliders', + exampleFile, + 'aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.sliderSelector); -}); + } +); // Keys -ariaTest('Right arrow increases slider value by 1', exampleFile, 'key-right-arrow', async (t) => { - - const sliders = await t.context.queryElements(t, ex.sliderSelector); +ariaTest( + 'Right arrow increases slider value by 1', + exampleFile, + 'key-right-arrow', + async (t) => { + const sliders = await t.context.queryElements(t, ex.sliderSelector); - // Send 1 key to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.ARROW_RIGHT); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow right key to the red slider, the value of the red slider should be 1' - ); - t.true( - await testDisplayMatchesValue(t, '1, 0, 0'), - 'Display should match rgb(1, 0, 0)' - ); - - // Send more than 255 keys to red slider - for (let i = 0; i < 260; i++) { + // Send 1 key to red slider + const redSlider = sliders[0]; await redSlider.sendKeys(Key.ARROW_RIGHT); - } - t.is( - await redSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 260 arrow right key, the value of the red slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 0, 0'), - 'Display should match rgb(255, 0, 0)' - ); - - // Send 1 key to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.ARROW_RIGHT); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow right key to the blue slider, the value of the green slider should be 1' - ); - t.true( - await testDisplayMatchesValue(t, '255, 1, 0'), - 'Display should match rgb(255, 1, 0)' - ); - - // Send more than 255 keys to green slider - for (let i = 0; i < 260; i++) { + t.is( + await redSlider.getAttribute('aria-valuenow'), + '1', + 'After sending 1 arrow right key to the red slider, the value of the red slider should be 1' + ); + t.true( + await testDisplayMatchesValue(t, '1, 0, 0'), + 'Display should match rgb(1, 0, 0)' + ); + + // Send more than 255 keys to red slider + for (let i = 0; i < 260; i++) { + await redSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 260 arrow right key, the value of the red slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 0, 0'), + 'Display should match rgb(255, 0, 0)' + ); + + // Send 1 key to green slider + const greenSlider = sliders[1]; await greenSlider.sendKeys(Key.ARROW_RIGHT); - } - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 260 arrow right key, the value of the green slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 0'), - 'Display should match rgb(255, 255, 0)' - ); - - // Send 1 key to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.ARROW_RIGHT); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow right key to the blue slider, the value of the blue slider should be 1' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 1'), - 'Display should match rgb(255, 255, 1)' - ); - - // Send more than 255 keys to blue slider - for (let i = 0; i < 260; i++) { + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '1', + 'After sending 1 arrow right key to the blue slider, the value of the green slider should be 1' + ); + t.true( + await testDisplayMatchesValue(t, '255, 1, 0'), + 'Display should match rgb(255, 1, 0)' + ); + + // Send more than 255 keys to green slider + for (let i = 0; i < 260; i++) { + await greenSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 260 arrow right key, the value of the green slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 0'), + 'Display should match rgb(255, 255, 0)' + ); + + // Send 1 key to blue slider + const blueSlider = sliders[2]; await blueSlider.sendKeys(Key.ARROW_RIGHT); + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '1', + 'After sending 1 arrow right key to the blue slider, the value of the blue slider should be 1' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 1'), + 'Display should match rgb(255, 255, 1)' + ); + + // Send more than 255 keys to blue slider + for (let i = 0; i < 260; i++) { + await blueSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 260 arrow right key, the value of the blue slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 255'), + 'Display should match rgb(255, 255, 255)' + ); } +); - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 260 arrow right key, the value of the blue slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 255'), - 'Display should match rgb(255, 255, 255)' - ); -}); - -ariaTest('up arrow increases slider value by 1', exampleFile, 'key-up-arrow', async (t) => { - - const sliders = await t.context.queryElements(t, ex.sliderSelector); +ariaTest( + 'up arrow increases slider value by 1', + exampleFile, + 'key-up-arrow', + async (t) => { + const sliders = await t.context.queryElements(t, ex.sliderSelector); - // Send 1 key to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.ARROW_UP); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow up key to the red slider, the value of the red slider should be 1' - ); - t.true( - await testDisplayMatchesValue(t, '1, 0, 0'), - 'Display should match rgb(1, 0, 0)' - ); - - // Send more than 255 keys to red slider - for (let i = 0; i < 260; i++) { + // Send 1 key to red slider + const redSlider = sliders[0]; await redSlider.sendKeys(Key.ARROW_UP); - } - t.is( - await redSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 260 arrow up key, the value of the red slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 0, 0'), - 'Display should match rgb(255, 0, 0)' - ); - - // Send 1 key to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.ARROW_UP); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow up key to the blue slider, the value of the green slider should be 1' - ); - t.true( - await testDisplayMatchesValue(t, '255, 1, 0'), - 'Display should match rgb(255, 1, 0)' - ); - - // Send more than 255 keys to green slider - for (let i = 0; i < 260; i++) { + t.is( + await redSlider.getAttribute('aria-valuenow'), + '1', + 'After sending 1 arrow up key to the red slider, the value of the red slider should be 1' + ); + t.true( + await testDisplayMatchesValue(t, '1, 0, 0'), + 'Display should match rgb(1, 0, 0)' + ); + + // Send more than 255 keys to red slider + for (let i = 0; i < 260; i++) { + await redSlider.sendKeys(Key.ARROW_UP); + } + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 260 arrow up key, the value of the red slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 0, 0'), + 'Display should match rgb(255, 0, 0)' + ); + + // Send 1 key to green slider + const greenSlider = sliders[1]; await greenSlider.sendKeys(Key.ARROW_UP); - } - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 260 arrow up key, the value of the green slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 0'), - 'Display should match rgb(255, 255, 0)' - ); - - // Send 1 key to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.ARROW_UP); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow up key to the blue slider, the value of the blue slider should be 1' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 1'), - 'Display should match rgb(255, 255, 1)' - ); - - // Send more than 255 keys to blue slider - for (let i = 0; i < 260; i++) { + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '1', + 'After sending 1 arrow up key to the blue slider, the value of the green slider should be 1' + ); + t.true( + await testDisplayMatchesValue(t, '255, 1, 0'), + 'Display should match rgb(255, 1, 0)' + ); + + // Send more than 255 keys to green slider + for (let i = 0; i < 260; i++) { + await greenSlider.sendKeys(Key.ARROW_UP); + } + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 260 arrow up key, the value of the green slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 0'), + 'Display should match rgb(255, 255, 0)' + ); + + // Send 1 key to blue slider + const blueSlider = sliders[2]; await blueSlider.sendKeys(Key.ARROW_UP); - } - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 260 arrow up key, the value of the blue slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 255'), - 'Display should match rgb(255, 255, 255)' - ); -}); + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '1', + 'After sending 1 arrow up key to the blue slider, the value of the blue slider should be 1' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 1'), + 'Display should match rgb(255, 255, 1)' + ); + + // Send more than 255 keys to blue slider + for (let i = 0; i < 260; i++) { + await blueSlider.sendKeys(Key.ARROW_UP); + } + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 260 arrow up key, the value of the blue slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 255'), + 'Display should match rgb(255, 255, 255)' + ); + } +); -ariaTest('page up increases slider value by 10', exampleFile, 'key-page-up', async (t) => { - - const sliders = await t.context.queryElements(t, ex.sliderSelector); +ariaTest( + 'page up increases slider value by 10', + exampleFile, + 'key-page-up', + async (t) => { + const sliders = await t.context.queryElements(t, ex.sliderSelector); - // Send 1 key to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.PAGE_UP); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '10', - 'After sending 1 page up key to the red slider, the value of the red slider should be 10' - ); - t.true( - await testDisplayMatchesValue(t, '10, 0, 0'), - 'Display should match rgb(10, 0, 0)' - ); - - // Send more than 26 keys to red slider - for (let i = 0; i < 26; i++) { + // Send 1 key to red slider + const redSlider = sliders[0]; await redSlider.sendKeys(Key.PAGE_UP); - } - t.is( - await redSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 26 page up key, the value of the red slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 0, 0'), - 'Display should match rgb(255, 0, 0)' - ); - - // Send 1 key to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.PAGE_UP); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '10', - 'After sending 1 page up key to the blue slider, the value of the green slider should be 10' - ); - t.true( - await testDisplayMatchesValue(t, '255, 10, 0'), - 'Display should match rgb(255, 10, 0)' - ); - - // Send more than 26 keys to green slider - for (let i = 0; i < 26; i++) { + t.is( + await redSlider.getAttribute('aria-valuenow'), + '10', + 'After sending 1 page up key to the red slider, the value of the red slider should be 10' + ); + t.true( + await testDisplayMatchesValue(t, '10, 0, 0'), + 'Display should match rgb(10, 0, 0)' + ); + + // Send more than 26 keys to red slider + for (let i = 0; i < 26; i++) { + await redSlider.sendKeys(Key.PAGE_UP); + } + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 26 page up key, the value of the red slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 0, 0'), + 'Display should match rgb(255, 0, 0)' + ); + + // Send 1 key to green slider + const greenSlider = sliders[1]; await greenSlider.sendKeys(Key.PAGE_UP); - } - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 260 page up key, the value of the green slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 0'), - 'Display should match rgb(255, 255, 0)' - ); - - // Send 1 key to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.PAGE_UP); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '10', - 'After sending 1 page up key to the blue slider, the value of the blue slider should be 10' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 10'), - 'Display should match rgb(255, 255, 10)' - ); - - // Send more than 26 keys to blue slider - for (let i = 0; i < 26; i++) { + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '10', + 'After sending 1 page up key to the blue slider, the value of the green slider should be 10' + ); + t.true( + await testDisplayMatchesValue(t, '255, 10, 0'), + 'Display should match rgb(255, 10, 0)' + ); + + // Send more than 26 keys to green slider + for (let i = 0; i < 26; i++) { + await greenSlider.sendKeys(Key.PAGE_UP); + } + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 260 page up key, the value of the green slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 0'), + 'Display should match rgb(255, 255, 0)' + ); + + // Send 1 key to blue slider + const blueSlider = sliders[2]; await blueSlider.sendKeys(Key.PAGE_UP); - } - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 26 page up key, the value of the blue slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 255'), - 'Display should match rgb(255, 255, 255)' - ); -}); - -ariaTest('key end set slider at max value', exampleFile, 'key-end', async (t) => { - - const sliders = await t.context.queryElements(t, ex.sliderSelector); + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '10', + 'After sending 1 page up key to the blue slider, the value of the blue slider should be 10' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 10'), + 'Display should match rgb(255, 255, 10)' + ); + + // Send more than 26 keys to blue slider + for (let i = 0; i < 26; i++) { + await blueSlider.sendKeys(Key.PAGE_UP); + } + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 26 page up key, the value of the blue slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 255'), + 'Display should match rgb(255, 255, 255)' + ); + } +); + +ariaTest( + 'key end set slider at max value', + exampleFile, + 'key-end', + async (t) => { + const sliders = await t.context.queryElements(t, ex.sliderSelector); + + // Send key end to red slider + const redSlider = sliders[0]; + await redSlider.sendKeys(Key.END); + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 1 end key to the red slider, the value of the red slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 0, 0'), + 'Display should match rgb(255, 0, 0)' + ); + + // Send key end to green slider + const greenSlider = sliders[1]; + await greenSlider.sendKeys(Key.END); + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 1 end key to the blue slider, the value of the green slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 0'), + 'Display should match rgb(255, 255, 0)' + ); + + // Send key end to blue slider + const blueSlider = sliders[2]; + await blueSlider.sendKeys(Key.END); + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '255', + 'After sending 1 end key to the blue slider, the value of the blue slider should be 255' + ); + t.true( + await testDisplayMatchesValue(t, '255, 255, 255'), + 'Display should match rgb(255, 255, 255)' + ); + } +); - // Send key end to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.END); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 1 end key to the red slider, the value of the red slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 0, 0'), - 'Display should match rgb(255, 0, 0)' - ); - - // Send key end to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.END); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 1 end key to the blue slider, the value of the green slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 0'), - 'Display should match rgb(255, 255, 0)' - ); - - // Send key end to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.END); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '255', - 'After sending 1 end key to the blue slider, the value of the blue slider should be 255' - ); - t.true( - await testDisplayMatchesValue(t, '255, 255, 255'), - 'Display should match rgb(255, 255, 255)' - ); -}); - -ariaTest('left arrow decreases slider value by 1', exampleFile, 'key-left-arrow', async (t) => { - - await sendAllSlidersToEnd(t); +ariaTest( + 'left arrow decreases slider value by 1', + exampleFile, + 'key-left-arrow', + async (t) => { + await sendAllSlidersToEnd(t); - const sliders = await t.context.queryElements(t, ex.sliderSelector); + const sliders = await t.context.queryElements(t, ex.sliderSelector); - // Send 1 key to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.ARROW_LEFT); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '254', - 'After sending 1 arrow left key to the red slider, the value of the red slider should be 254' - ); - t.true( - await testDisplayMatchesValue(t, '254, 255, 255'), - 'Display should match rgb(254, 255, 255)' - ); - - // Send more than 255 keys to red slider - for (let i = 0; i < 260; i++) { + // Send 1 key to red slider + const redSlider = sliders[0]; await redSlider.sendKeys(Key.ARROW_LEFT); - } - t.is( - await redSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 260 arrow left key, the value of the red slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 255, 255'), - 'Display should match rgb(0, 255, 255)' - ); - - // Send 1 key to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.ARROW_LEFT); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '254', - 'After sending 1 arrow left key to the blue slider, the value of the green slider should be 254' - ); - t.true( - await testDisplayMatchesValue(t, '0, 254, 255'), - 'Display should match rgb(0, 254, 255)' - ); - - // Send more than 255 keys to green slider - for (let i = 0; i < 260; i++) { + t.is( + await redSlider.getAttribute('aria-valuenow'), + '254', + 'After sending 1 arrow left key to the red slider, the value of the red slider should be 254' + ); + t.true( + await testDisplayMatchesValue(t, '254, 255, 255'), + 'Display should match rgb(254, 255, 255)' + ); + + // Send more than 255 keys to red slider + for (let i = 0; i < 260; i++) { + await redSlider.sendKeys(Key.ARROW_LEFT); + } + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 260 arrow left key, the value of the red slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 255, 255'), + 'Display should match rgb(0, 255, 255)' + ); + + // Send 1 key to green slider + const greenSlider = sliders[1]; await greenSlider.sendKeys(Key.ARROW_LEFT); - } - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 260 arrow left key, the value of the green slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 255'), - 'Display should match rgb(0, 0, 255)' - ); - - // Send 1 key to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.ARROW_LEFT); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '254', - 'After sending 1 arrow left key to the blue slider, the value of the blue slider should be 254' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 254'), - 'Display should match rgb(0, 0, 254)' - ); - - // Send more than 255 keys to blue slider - for (let i = 0; i < 260; i++) { + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '254', + 'After sending 1 arrow left key to the blue slider, the value of the green slider should be 254' + ); + t.true( + await testDisplayMatchesValue(t, '0, 254, 255'), + 'Display should match rgb(0, 254, 255)' + ); + + // Send more than 255 keys to green slider + for (let i = 0; i < 260; i++) { + await greenSlider.sendKeys(Key.ARROW_LEFT); + } + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 260 arrow left key, the value of the green slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 255'), + 'Display should match rgb(0, 0, 255)' + ); + + // Send 1 key to blue slider + const blueSlider = sliders[2]; await blueSlider.sendKeys(Key.ARROW_LEFT); + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '254', + 'After sending 1 arrow left key to the blue slider, the value of the blue slider should be 254' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 254'), + 'Display should match rgb(0, 0, 254)' + ); + + // Send more than 255 keys to blue slider + for (let i = 0; i < 260; i++) { + await blueSlider.sendKeys(Key.ARROW_LEFT); + } + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 260 arrow left key, the value of the blue slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 0'), + 'Display should match rgb(0, 0, 0)' + ); } +); - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 260 arrow left key, the value of the blue slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 0'), - 'Display should match rgb(0, 0, 0)' - ); -}); - -ariaTest('down arrow decreases slider value by 1', exampleFile, 'key-down-arrow', async (t) => { - - await sendAllSlidersToEnd(t); +ariaTest( + 'down arrow decreases slider value by 1', + exampleFile, + 'key-down-arrow', + async (t) => { + await sendAllSlidersToEnd(t); - const sliders = await t.context.queryElements(t, ex.sliderSelector); + const sliders = await t.context.queryElements(t, ex.sliderSelector); - // Send 1 key to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.ARROW_DOWN); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '254', - 'After sending 1 arrow down key to the red slider, the value of the red slider should be 254' - ); - t.true( - await testDisplayMatchesValue(t, '254, 255, 255'), - 'Display should match rgb(254, 255, 255)' - ); - - // Send more than 255 keys to red slider - for (let i = 0; i < 260; i++) { + // Send 1 key to red slider + const redSlider = sliders[0]; await redSlider.sendKeys(Key.ARROW_DOWN); - } - t.is( - await redSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 260 arrow down key, the value of the red slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 255, 255'), - 'Display should match rgb(0, 255, 255)' - ); - - // Send 1 key to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.ARROW_DOWN); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '254', - 'After sending 1 arrow down key to the blue slider, the value of the green slider should be 254' - ); - t.true( - await testDisplayMatchesValue(t, '0, 254, 255'), - 'Display should match rgb(0, 254, 255)' - ); - - // Send more than 255 keys to green slider - for (let i = 0; i < 260; i++) { + t.is( + await redSlider.getAttribute('aria-valuenow'), + '254', + 'After sending 1 arrow down key to the red slider, the value of the red slider should be 254' + ); + t.true( + await testDisplayMatchesValue(t, '254, 255, 255'), + 'Display should match rgb(254, 255, 255)' + ); + + // Send more than 255 keys to red slider + for (let i = 0; i < 260; i++) { + await redSlider.sendKeys(Key.ARROW_DOWN); + } + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 260 arrow down key, the value of the red slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 255, 255'), + 'Display should match rgb(0, 255, 255)' + ); + + // Send 1 key to green slider + const greenSlider = sliders[1]; await greenSlider.sendKeys(Key.ARROW_DOWN); - } - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 260 arrow down key, the value of the green slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 255'), - 'Display should match rgb(0, 0, 255)' - ); - - // Send 1 key to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.ARROW_DOWN); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '254', - 'After sending 1 arrow down key to the blue slider, the value of the blue slider should be 254' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 254'), - 'Display should match rgb(0, 0, 254)' - ); - - // Send more than 255 keys to blue slider - for (let i = 0; i < 260; i++) { + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '254', + 'After sending 1 arrow down key to the blue slider, the value of the green slider should be 254' + ); + t.true( + await testDisplayMatchesValue(t, '0, 254, 255'), + 'Display should match rgb(0, 254, 255)' + ); + + // Send more than 255 keys to green slider + for (let i = 0; i < 260; i++) { + await greenSlider.sendKeys(Key.ARROW_DOWN); + } + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 260 arrow down key, the value of the green slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 255'), + 'Display should match rgb(0, 0, 255)' + ); + + // Send 1 key to blue slider + const blueSlider = sliders[2]; await blueSlider.sendKeys(Key.ARROW_DOWN); + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '254', + 'After sending 1 arrow down key to the blue slider, the value of the blue slider should be 254' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 254'), + 'Display should match rgb(0, 0, 254)' + ); + + // Send more than 255 keys to blue slider + for (let i = 0; i < 260; i++) { + await blueSlider.sendKeys(Key.ARROW_DOWN); + } + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 260 arrow down key, the value of the blue slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 0'), + 'Display should match rgb(0, 0, 0)' + ); } +); - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 260 arrow down key, the value of the blue slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 0'), - 'Display should match rgb(0, 0, 0)' - ); -}); - -ariaTest('page down decreases slider value by 10', exampleFile, 'key-page-down', async (t) => { - - await sendAllSlidersToEnd(t); +ariaTest( + 'page down decreases slider value by 10', + exampleFile, + 'key-page-down', + async (t) => { + await sendAllSlidersToEnd(t); - const sliders = await t.context.queryElements(t, ex.sliderSelector); + const sliders = await t.context.queryElements(t, ex.sliderSelector); - // Send 1 key to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.PAGE_DOWN); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '245', - 'After sending 1 page down key to the red slider, the value of the red slider should be 245' - ); - t.true( - await testDisplayMatchesValue(t, '245, 255, 255'), - 'Display should match rgb(245, 255, 255)' - ); - - // Send more than 25 keys to red slider - for (let i = 0; i < 26; i++) { + // Send 1 key to red slider + const redSlider = sliders[0]; await redSlider.sendKeys(Key.PAGE_DOWN); - } - t.is( - await redSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 26 page down key, the value of the red slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 255, 255'), - 'Display should match rgb(0, 255, 255)' - ); - - // Send 1 key to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.PAGE_DOWN); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '245', - 'After sending 1 page down key to the blue slider, the value of the green slider should be 245' - ); - t.true( - await testDisplayMatchesValue(t, '0, 245, 255'), - 'Display should match rgb(0, 245, 255)' - ); - - // Send more than 25 keys to green slider - for (let i = 0; i < 26; i++) { + t.is( + await redSlider.getAttribute('aria-valuenow'), + '245', + 'After sending 1 page down key to the red slider, the value of the red slider should be 245' + ); + t.true( + await testDisplayMatchesValue(t, '245, 255, 255'), + 'Display should match rgb(245, 255, 255)' + ); + + // Send more than 25 keys to red slider + for (let i = 0; i < 26; i++) { + await redSlider.sendKeys(Key.PAGE_DOWN); + } + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 26 page down key, the value of the red slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 255, 255'), + 'Display should match rgb(0, 255, 255)' + ); + + // Send 1 key to green slider + const greenSlider = sliders[1]; await greenSlider.sendKeys(Key.PAGE_DOWN); - } - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 26 page down key, the value of the green slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 255'), - 'Display should match rgb(0, 0, 255)' - ); - - // Send 1 key to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.PAGE_DOWN); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '245', - 'After sending 1 page down key to the blue slider, the value of the blue slider should be 245' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 245'), - 'Display should match rgb(0, 0, 245)' - ); - - // Send more than 25 keys to blue slider - for (let i = 0; i < 26; i++) { + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '245', + 'After sending 1 page down key to the blue slider, the value of the green slider should be 245' + ); + t.true( + await testDisplayMatchesValue(t, '0, 245, 255'), + 'Display should match rgb(0, 245, 255)' + ); + + // Send more than 25 keys to green slider + for (let i = 0; i < 26; i++) { + await greenSlider.sendKeys(Key.PAGE_DOWN); + } + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 26 page down key, the value of the green slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 255'), + 'Display should match rgb(0, 0, 255)' + ); + + // Send 1 key to blue slider + const blueSlider = sliders[2]; await blueSlider.sendKeys(Key.PAGE_DOWN); - } - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 26 page down key, the value of the blue slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 0'), - 'Display should match rgb(0, 0, 0)' - ); -}); - -ariaTest('home set slider value to minimum', exampleFile, 'key-home', async (t) => { - - const sliders = await t.context.queryElements(t, ex.sliderSelector); - - await sendAllSlidersToEnd(t); - - // Send key end to red slider - const redSlider = sliders[0]; - await redSlider.sendKeys(Key.HOME); - - t.is( - await redSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 1 home key to the red slider, the value of the red slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 255, 255'), - 'Display should match rgb(0, 255, 255)' - ); - - // Send key home to green slider - const greenSlider = sliders[1]; - await greenSlider.sendKeys(Key.HOME); - - t.is( - await greenSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 1 home key to the blue slider, the value of the green slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 255'), - 'Display should match rgb(0, 0, 255)' - ); - - // Send key home to blue slider - const blueSlider = sliders[2]; - await blueSlider.sendKeys(Key.HOME); - - t.is( - await blueSlider.getAttribute('aria-valuenow'), - '0', - 'After sending 1 home key to the blue slider, the value of the blue slider should be 0' - ); - t.true( - await testDisplayMatchesValue(t, '0, 0, 0'), - 'Display should match rgb(0, 0, 0)' - ); -}); + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '245', + 'After sending 1 page down key to the blue slider, the value of the blue slider should be 245' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 245'), + 'Display should match rgb(0, 0, 245)' + ); + + // Send more than 25 keys to blue slider + for (let i = 0; i < 26; i++) { + await blueSlider.sendKeys(Key.PAGE_DOWN); + } + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 26 page down key, the value of the blue slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 0'), + 'Display should match rgb(0, 0, 0)' + ); + } +); + +ariaTest( + 'home set slider value to minimum', + exampleFile, + 'key-home', + async (t) => { + const sliders = await t.context.queryElements(t, ex.sliderSelector); + + await sendAllSlidersToEnd(t); + + // Send key end to red slider + const redSlider = sliders[0]; + await redSlider.sendKeys(Key.HOME); + + t.is( + await redSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 1 home key to the red slider, the value of the red slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 255, 255'), + 'Display should match rgb(0, 255, 255)' + ); + + // Send key home to green slider + const greenSlider = sliders[1]; + await greenSlider.sendKeys(Key.HOME); + + t.is( + await greenSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 1 home key to the blue slider, the value of the green slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 255'), + 'Display should match rgb(0, 0, 255)' + ); + + // Send key home to blue slider + const blueSlider = sliders[2]; + await blueSlider.sendKeys(Key.HOME); + + t.is( + await blueSlider.getAttribute('aria-valuenow'), + '0', + 'After sending 1 home key to the blue slider, the value of the blue slider should be 0' + ); + t.true( + await testDisplayMatchesValue(t, '0, 0, 0'), + 'Display should match rgb(0, 0, 0)' + ); + } +); diff --git a/test/tests/slider_slider-2.js b/test/tests/slider_slider-2.js index 747268e0c8..b9849ca69a 100644 --- a/test/tests/slider_slider-2.js +++ b/test/tests/slider_slider-2.js @@ -22,17 +22,8 @@ const ex = { fanMin: '0', heatMax: '2', heatMin: '0', - fanValues: [ - 'Off', - 'Low', - 'High', - 'Auto' - ], - heatValues: [ - 'Off', - 'Heat', - 'Cool' - ] + fanValues: ['Off', 'Low', 'High', 'Auto'], + heatValues: ['Off', 'Heat', 'Cool'], }; const sendAllSlidersToEnd = async function (t) { @@ -52,516 +43,757 @@ const getValueAndText = async function (t, selector) { // Attributes -ariaTest('role="slider" on div element', exampleFile, 'slider-role', async (t) => { +ariaTest( + 'role="slider" on div element', + exampleFile, + 'slider-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'slider', '3', 'div'); -}); + } +); -ariaTest('"tabindex" set to "0" on sliders', exampleFile, 'tabindex', async (t) => { +ariaTest( + '"tabindex" set to "0" on sliders', + exampleFile, + 'tabindex', + async (t) => { await assertAttributeValues(t, ex.sliderSelector, 'tabindex', '0'); -}); - -ariaTest('"aria-orientation" set on sliders', exampleFile, 'aria-orientation', async (t) => { - await assertAttributeValues(t, ex.tempSelector, 'aria-orientation', 'vertical'); - await assertAttributeValues(t, ex.fanSelector, 'aria-orientation', 'horizontal'); - await assertAttributeValues(t, ex.heatSelector, 'aria-orientation', 'horizontal'); -}); - -ariaTest('"aria-valuemax" set on sliders', exampleFile, 'aria-valuemax', async (t) => { - await assertAttributeValues(t, ex.tempSelector, 'aria-valuemax', ex.tempMax); - await assertAttributeValues(t, ex.fanSelector, 'aria-valuemax', ex.fanMax); - await assertAttributeValues(t, ex.heatSelector, 'aria-valuemax', ex.heatMax); -}); - -ariaTest('"aria-valuemin" set on sliders', exampleFile, 'aria-valuemin', async (t) => { - await assertAttributeValues(t, ex.tempSelector, 'aria-valuemin', ex.tempMin); - await assertAttributeValues(t, ex.fanSelector, 'aria-valuemin', ex.fanMin); - await assertAttributeValues(t, ex.heatSelector, 'aria-valuemin', ex.heatMin); -}); - -ariaTest('"aria-valuenow" reflects slider value', exampleFile, 'aria-valuenow', async (t) => { - await assertAttributeValues(t, ex.tempSelector, 'aria-valuenow', ex.tempDefault); - await assertAttributeValues(t, ex.fanSelector, 'aria-valuenow', '0'); - await assertAttributeValues(t, ex.heatSelector, 'aria-valuenow', '0'); -}); - -ariaTest('"aria-valuetext" reflects slider value', exampleFile, 'aria-valuetext', async (t) => { - await assertAttributeValues(t, ex.fanSelector, 'aria-valuetext', ex.fanValues[0]); - await assertAttributeValues(t, ex.heatSelector, 'aria-valuetext', ex.heatValues[0]); -}); - -ariaTest('"aria-labelledby" set on sliders', exampleFile, 'aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.sliderSelector); -}); + } +); + +ariaTest( + '"aria-orientation" set on sliders', + exampleFile, + 'aria-orientation', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-orientation', + 'vertical' + ); + await assertAttributeValues( + t, + ex.fanSelector, + 'aria-orientation', + 'horizontal' + ); + await assertAttributeValues( + t, + ex.heatSelector, + 'aria-orientation', + 'horizontal' + ); + } +); + +ariaTest( + '"aria-valuemax" set on sliders', + exampleFile, + 'aria-valuemax', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuemax', + ex.tempMax + ); + await assertAttributeValues(t, ex.fanSelector, 'aria-valuemax', ex.fanMax); + await assertAttributeValues( + t, + ex.heatSelector, + 'aria-valuemax', + ex.heatMax + ); + } +); + +ariaTest( + '"aria-valuemin" set on sliders', + exampleFile, + 'aria-valuemin', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuemin', + ex.tempMin + ); + await assertAttributeValues(t, ex.fanSelector, 'aria-valuemin', ex.fanMin); + await assertAttributeValues( + t, + ex.heatSelector, + 'aria-valuemin', + ex.heatMin + ); + } +); + +ariaTest( + '"aria-valuenow" reflects slider value', + exampleFile, + 'aria-valuenow', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuenow', + ex.tempDefault + ); + await assertAttributeValues(t, ex.fanSelector, 'aria-valuenow', '0'); + await assertAttributeValues(t, ex.heatSelector, 'aria-valuenow', '0'); + } +); + +ariaTest( + '"aria-valuetext" reflects slider value', + exampleFile, + 'aria-valuetext', + async (t) => { + await assertAttributeValues( + t, + ex.fanSelector, + 'aria-valuetext', + ex.fanValues[0] + ); + await assertAttributeValues( + t, + ex.heatSelector, + 'aria-valuetext', + ex.heatValues[0] + ); + } +); +ariaTest( + '"aria-labelledby" set on sliders', + exampleFile, + 'aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.sliderSelector); + } +); // Keys -ariaTest('Right arrow increases slider value by 1', exampleFile, 'key-right-arrow', async (t) => { - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.ARROW_RIGHT); - - let sliderVal = parseInt(ex.tempDefault) + 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 arrow right key to the temp slider, "aria-valuenow": ' + sliderVal - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { +ariaTest( + 'Right arrow increases slider value by 1', + exampleFile, + 'key-right-arrow', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); await tempSlider.sendKeys(Key.ARROW_RIGHT); - } - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending 52 arrow right key, the value of the temp slider should be: ' + ex.tempMax - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); - - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement(By.css(ex.fanSelector)); - await fanSlider.sendKeys(Key.ARROW_RIGHT); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['1', ex.fanValues[1]], - 'After sending 1 arrow right key to the fan slider, aria-valuenow should be "1" and aria-value-text should be: ' + ex.fanValues[1] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { + let sliderVal = parseInt(ex.tempDefault) + 1; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 arrow right key to the temp slider, "aria-valuenow": ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + sliderVal.toString(), + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send 51 more keys to temp slider + for (let i = 0; i < 51; i++) { + await tempSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 52 arrow right key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMax, + 'Temp display should match value of slider: ' + ex.tempMax + ); + + // Send 1 key to fan slider + const fanSlider = await t.context.session.findElement( + By.css(ex.fanSelector) + ); await fanSlider.sendKeys(Key.ARROW_RIGHT); - } - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], - 'After sending 5 arrow right key to the fan slider, aria-valuenow should be "' + ex.fanMax + '" and aria-value-text should be: ' + ex.fanValues[parseInt(ex.fanMax)] - ); - - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement(By.css(ex.heatSelector)); - await heatSlider.sendKeys(Key.ARROW_RIGHT); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['1', ex.heatValues[1]], - 'After sending 1 arrow right key to the heat slider, aria-valuenow should be "1" and aria-value-text should be: ' + ex.heatValues[1] - ); - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + ['1', ex.fanValues[1]], + 'After sending 1 arrow right key to the fan slider, aria-valuenow should be "1" and aria-value-text should be: ' + + ex.fanValues[1] + ); + + // Send more than 5 keys to fan slider + for (let i = 0; i < 5; i++) { + await fanSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], + 'After sending 5 arrow right key to the fan slider, aria-valuenow should be "' + + ex.fanMax + + '" and aria-value-text should be: ' + + ex.fanValues[parseInt(ex.fanMax)] + ); + + // Send 1 key to heat slider + const heatSlider = await t.context.session.findElement( + By.css(ex.heatSelector) + ); await heatSlider.sendKeys(Key.ARROW_RIGHT); - } - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], - 'After sending 5 arrow right key to the heat slider, aria-valuenow should be "' + ex.heatMax + '" and aria-value-text should be: ' + ex.heatValues[parseInt(ex.heatMax)] - ); -}); - -ariaTest('up arrow increases slider value by 1', exampleFile, 'key-up-arrow', async (t) => { - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.ARROW_UP); - - let sliderVal = parseInt(ex.tempDefault) + 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 arrow up key to the temp slider, "aria-valuenow": ' + sliderVal - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { - await tempSlider.sendKeys(Key.ARROW_UP); + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + ['1', ex.heatValues[1]], + 'After sending 1 arrow right key to the heat slider, aria-valuenow should be "1" and aria-value-text should be: ' + + ex.heatValues[1] + ); + + // Send more than 5 keys to heat slider + for (let i = 0; i < 5; i++) { + await heatSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], + 'After sending 5 arrow right key to the heat slider, aria-valuenow should be "' + + ex.heatMax + + '" and aria-value-text should be: ' + + ex.heatValues[parseInt(ex.heatMax)] + ); } +); + +ariaTest( + 'up arrow increases slider value by 1', + exampleFile, + 'key-up-arrow', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_UP); - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending 52 arrow up key, the value of the temp slider should be: ' + ex.tempMax - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); - - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement(By.css(ex.fanSelector)); - await fanSlider.sendKeys(Key.ARROW_UP); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['1', ex.fanValues[1]], - 'After sending 1 arrow up key to the fan slider, aria-valuenow should be "1" and aria-value-text should be: ' + ex.fanValues[1] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { + let sliderVal = parseInt(ex.tempDefault) + 1; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 arrow up key to the temp slider, "aria-valuenow": ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + sliderVal.toString(), + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send 51 more keys to temp slider + for (let i = 0; i < 51; i++) { + await tempSlider.sendKeys(Key.ARROW_UP); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 52 arrow up key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMax, + 'Temp display should match value of slider: ' + ex.tempMax + ); + + // Send 1 key to fan slider + const fanSlider = await t.context.session.findElement( + By.css(ex.fanSelector) + ); await fanSlider.sendKeys(Key.ARROW_UP); - } - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], - 'After sending 6 arrow up key to the fan slider, aria-valuenow should be "' + ex.fanMax + '" and aria-value-text should be: ' + ex.fanValues[parseInt(ex.fanMax)] - ); - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement(By.css(ex.heatSelector)); - await heatSlider.sendKeys(Key.ARROW_UP); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['1', ex.heatValues[1]], - 'After sending 1 arrow up key to the heat slider, aria-valuenow should be "1" and aria-value-text should be: ' + ex.heatValues[1] - ); - - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + ['1', ex.fanValues[1]], + 'After sending 1 arrow up key to the fan slider, aria-valuenow should be "1" and aria-value-text should be: ' + + ex.fanValues[1] + ); + + // Send more than 5 keys to fan slider + for (let i = 0; i < 5; i++) { + await fanSlider.sendKeys(Key.ARROW_UP); + } + + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], + 'After sending 6 arrow up key to the fan slider, aria-valuenow should be "' + + ex.fanMax + + '" and aria-value-text should be: ' + + ex.fanValues[parseInt(ex.fanMax)] + ); + + // Send 1 key to heat slider + const heatSlider = await t.context.session.findElement( + By.css(ex.heatSelector) + ); await heatSlider.sendKeys(Key.ARROW_UP); - } - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], - 'After sending 6 arrow up key to the heat slider, aria-valuenow should be "' + ex.heatMax + '" and aria-value-text should be: ' + ex.heatValues[parseInt(ex.heatMax)] - ); -}); - - -ariaTest('page up increases slider value by 10', exampleFile, 'key-page-up', async (t) => { - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.PAGE_UP); - - let sliderVal = parseInt(ex.tempDefault) + 10; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 page up key to the temp slider, the value of the temp slider should be: ' + sliderVal - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send more than 5 keys to temp slider - for (let i = 0; i < 5; i++) { - await tempSlider.sendKeys(Key.PAGE_UP); + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + ['1', ex.heatValues[1]], + 'After sending 1 arrow up key to the heat slider, aria-valuenow should be "1" and aria-value-text should be: ' + + ex.heatValues[1] + ); + + // Send more than 5 keys to heat slider + for (let i = 0; i < 5; i++) { + await heatSlider.sendKeys(Key.ARROW_UP); + } + + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], + 'After sending 6 arrow up key to the heat slider, aria-valuenow should be "' + + ex.heatMax + + '" and aria-value-text should be: ' + + ex.heatValues[parseInt(ex.heatMax)] + ); } +); + +ariaTest( + 'page up increases slider value by 10', + exampleFile, + 'key-page-up', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.PAGE_UP); - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending 5 page up key, the value of the temp slider should be: ' + ex.tempMax - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); -}); - -ariaTest('key end set slider at max value', exampleFile, 'key-end', async (t) => { - - // Send key end to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.END); - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending key END, the value of the temp slider should be: ' + ex.tempMax - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); - - // Send key end to fan slider - const fanSlider = await t.context.session.findElement(By.css(ex.fanSelector)); - await fanSlider.sendKeys(Key.END); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], - 'After sending key end to the heat slider, aria-valuenow should be "' + ex.fanMax + '" and aria-value-text should be: ' + ex.fanValues[parseInt(ex.fanMax)] - ); - - // Send key end to heat slider - const heatSlider = await t.context.session.findElement(By.css(ex.heatSelector)); - await heatSlider.sendKeys(Key.END); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], - 'After sending key end to the heat slider, aria-valuenow should be "' + ex.heatMax + '" and aria-value-text should be: ' + ex.heatValues[parseInt(ex.heatMax)] - ); -}); - -ariaTest('left arrow decreases slider value by 1', exampleFile, 'key-left-arrow', async (t) => { - - await sendAllSlidersToEnd(t); - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.ARROW_LEFT); - - let tempVal = parseInt(ex.tempMax) - 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - tempVal.toString(), - 'After sending 1 arrow left key, the value of the temp slider should be: ' + tempVal - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - tempVal.toString(), - 'Temp display should match value of slider: ' + tempVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { - await tempSlider.sendKeys(Key.ARROW_LEFT); + let sliderVal = parseInt(ex.tempDefault) + 10; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 page up key to the temp slider, the value of the temp slider should be: ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + sliderVal.toString(), + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send more than 5 keys to temp slider + for (let i = 0; i < 5; i++) { + await tempSlider.sendKeys(Key.PAGE_UP); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 5 page up key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMax, + 'Temp display should match value of slider: ' + ex.tempMax + ); } +); + +ariaTest( + 'key end set slider at max value', + exampleFile, + 'key-end', + async (t) => { + // Send key end to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.END); + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending key END, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMax, + 'Temp display should match value of slider: ' + ex.tempMax + ); + + // Send key end to fan slider + const fanSlider = await t.context.session.findElement( + By.css(ex.fanSelector) + ); + await fanSlider.sendKeys(Key.END); + + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], + 'After sending key end to the heat slider, aria-valuenow should be "' + + ex.fanMax + + '" and aria-value-text should be: ' + + ex.fanValues[parseInt(ex.fanMax)] + ); + + // Send key end to heat slider + const heatSlider = await t.context.session.findElement( + By.css(ex.heatSelector) + ); + await heatSlider.sendKeys(Key.END); + + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], + 'After sending key end to the heat slider, aria-valuenow should be "' + + ex.heatMax + + '" and aria-value-text should be: ' + + ex.heatValues[parseInt(ex.heatMax)] + ); + } +); + +ariaTest( + 'left arrow decreases slider value by 1', + exampleFile, + 'key-left-arrow', + async (t) => { + await sendAllSlidersToEnd(t); + + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_LEFT); - - let sliderVal = parseInt(ex.tempDefault) + 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin.toString(), - 'After sending 53 arrow left key to the temp slider, "aria-valuenow": ' + ex.tempMin - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMin.toString(), - 'Temp display should match value of slider: ' + ex.tempMin - ); - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement(By.css(ex.fanSelector)); - await fanSlider.sendKeys(Key.ARROW_LEFT); - - let fanVal = parseInt(ex.fanMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [fanVal.toString(), ex.fanValues[fanVal]], - 'After sending 1 arrow left key to the fan slider, aria-valuenow should be "' + fanVal + '" and aria-value-text should be: ' + ex.fanValues[fanVal] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { + let tempVal = parseInt(ex.tempMax) - 1; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + tempVal.toString(), + 'After sending 1 arrow left key, the value of the temp slider should be: ' + + tempVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + tempVal.toString(), + 'Temp display should match value of slider: ' + tempVal + ); + + // Send 51 more keys to temp slider + for (let i = 0; i < 51; i++) { + await tempSlider.sendKeys(Key.ARROW_LEFT); + } + + let sliderVal = parseInt(ex.tempDefault) + 1; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin.toString(), + 'After sending 53 arrow left key to the temp slider, "aria-valuenow": ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMin.toString(), + 'Temp display should match value of slider: ' + ex.tempMin + ); + + // Send 1 key to fan slider + const fanSlider = await t.context.session.findElement( + By.css(ex.fanSelector) + ); await fanSlider.sendKeys(Key.ARROW_LEFT); - } - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['0', ex.fanValues[0]], - 'After sending 6 arrow left key to the fan slider, aria-valuenow should be "0" and aria-value-text should be: ' + ex.fanValues[0] - ); - - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement(By.css(ex.heatSelector)); - await heatSlider.sendKeys(Key.ARROW_LEFT); - - let heatVal = parseInt(ex.heatMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [heatVal.toString(), ex.heatValues[heatVal]], - 'After sending 1 arrow left key to the heat slider, aria-valuenow should be "' + heatVal + '" and aria-value-text should be: ' + ex.heatValues[heatVal] - ); - - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { + let fanVal = parseInt(ex.fanMax) - 1; + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + [fanVal.toString(), ex.fanValues[fanVal]], + 'After sending 1 arrow left key to the fan slider, aria-valuenow should be "' + + fanVal + + '" and aria-value-text should be: ' + + ex.fanValues[fanVal] + ); + + // Send more than 5 keys to fan slider + for (let i = 0; i < 5; i++) { + await fanSlider.sendKeys(Key.ARROW_LEFT); + } + + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + ['0', ex.fanValues[0]], + 'After sending 6 arrow left key to the fan slider, aria-valuenow should be "0" and aria-value-text should be: ' + + ex.fanValues[0] + ); + + // Send 1 key to heat slider + const heatSlider = await t.context.session.findElement( + By.css(ex.heatSelector) + ); await heatSlider.sendKeys(Key.ARROW_LEFT); - } - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['0', ex.heatValues[0]], - 'After sending 6 arrow left key to the heat slider, aria-valuenow should be "0" and aria-value-text should be: ' + ex.heatValues[0] - ); -}); - -ariaTest('down arrow decreases slider value by 1', exampleFile, 'key-down-arrow', async (t) => { - - await sendAllSlidersToEnd(t); - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.ARROW_DOWN); - - let tempVal = parseInt(ex.tempMax) - 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - tempVal.toString(), - 'After sending 1 arrow down key, the value of the temp slider should be: ' + tempVal - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - tempVal.toString(), - 'Temp display should match value of slider: ' + tempVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { - await tempSlider.sendKeys(Key.ARROW_DOWN); + let heatVal = parseInt(ex.heatMax) - 1; + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + [heatVal.toString(), ex.heatValues[heatVal]], + 'After sending 1 arrow left key to the heat slider, aria-valuenow should be "' + + heatVal + + '" and aria-value-text should be: ' + + ex.heatValues[heatVal] + ); + + // Send more than 5 keys to heat slider + for (let i = 0; i < 5; i++) { + await heatSlider.sendKeys(Key.ARROW_LEFT); + } + + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + ['0', ex.heatValues[0]], + 'After sending 6 arrow left key to the heat slider, aria-valuenow should be "0" and aria-value-text should be: ' + + ex.heatValues[0] + ); } +); + +ariaTest( + 'down arrow decreases slider value by 1', + exampleFile, + 'key-down-arrow', + async (t) => { + await sendAllSlidersToEnd(t); + + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_DOWN); - - let sliderVal = parseInt(ex.tempDefault) + 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin.toString(), - 'After sending 53 arrow down key to the temp slider, "aria-valuenow": ' + ex.tempMin - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMin.toString(), - 'Temp display should match value of slider: ' + ex.tempMin - ); - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement(By.css(ex.fanSelector)); - await fanSlider.sendKeys(Key.ARROW_DOWN); - - let fanVal = parseInt(ex.fanMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [fanVal.toString(), ex.fanValues[fanVal]], - 'After sending 1 arrow down key to the fan slider, aria-valuenow should be "' + fanVal + '" and aria-value-text should be: ' + ex.fanValues[fanVal] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { + let tempVal = parseInt(ex.tempMax) - 1; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + tempVal.toString(), + 'After sending 1 arrow down key, the value of the temp slider should be: ' + + tempVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + tempVal.toString(), + 'Temp display should match value of slider: ' + tempVal + ); + + // Send 51 more keys to temp slider + for (let i = 0; i < 51; i++) { + await tempSlider.sendKeys(Key.ARROW_DOWN); + } + + let sliderVal = parseInt(ex.tempDefault) + 1; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin.toString(), + 'After sending 53 arrow down key to the temp slider, "aria-valuenow": ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMin.toString(), + 'Temp display should match value of slider: ' + ex.tempMin + ); + + // Send 1 key to fan slider + const fanSlider = await t.context.session.findElement( + By.css(ex.fanSelector) + ); await fanSlider.sendKeys(Key.ARROW_DOWN); - } - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['0', ex.fanValues[0]], - 'After sending 6 arrow down key to the fan slider, aria-valuenow should be "0" and aria-value-text should be: ' + ex.fanValues[0] - ); - - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement(By.css(ex.heatSelector)); - await heatSlider.sendKeys(Key.ARROW_DOWN); - - let heatVal = parseInt(ex.heatMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [heatVal.toString(), ex.heatValues[heatVal]], - 'After sending 1 arrow down key to the heat slider, aria-valuenow should be "' + heatVal + '" and aria-value-text should be: ' + ex.heatValues[heatVal] - ); - - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { + let fanVal = parseInt(ex.fanMax) - 1; + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + [fanVal.toString(), ex.fanValues[fanVal]], + 'After sending 1 arrow down key to the fan slider, aria-valuenow should be "' + + fanVal + + '" and aria-value-text should be: ' + + ex.fanValues[fanVal] + ); + + // Send more than 5 keys to fan slider + for (let i = 0; i < 5; i++) { + await fanSlider.sendKeys(Key.ARROW_DOWN); + } + + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + ['0', ex.fanValues[0]], + 'After sending 6 arrow down key to the fan slider, aria-valuenow should be "0" and aria-value-text should be: ' + + ex.fanValues[0] + ); + + // Send 1 key to heat slider + const heatSlider = await t.context.session.findElement( + By.css(ex.heatSelector) + ); await heatSlider.sendKeys(Key.ARROW_DOWN); - } - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['0', ex.heatValues[0]], - 'After sending 6 arrow downx key to the heat slider, aria-valuenow should be "0" and aria-value-text should be: ' + ex.heatValues[0] - ); -}); - -ariaTest('page down decreases slider value by 10', exampleFile, 'key-page-down', async (t) => { - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.PAGE_DOWN); - - let sliderVal = parseInt(ex.tempDefault) - 10; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 page down key to the temp slider, the value of the temp slider should be: ' + sliderVal - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send more than 5 keys to temp slider - for (let i = 0; i < 5; i++) { - await tempSlider.sendKeys(Key.PAGE_DOWN); + let heatVal = parseInt(ex.heatMax) - 1; + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + [heatVal.toString(), ex.heatValues[heatVal]], + 'After sending 1 arrow down key to the heat slider, aria-valuenow should be "' + + heatVal + + '" and aria-value-text should be: ' + + ex.heatValues[heatVal] + ); + + // Send more than 5 keys to heat slider + for (let i = 0; i < 5; i++) { + await heatSlider.sendKeys(Key.ARROW_DOWN); + } + + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + ['0', ex.heatValues[0]], + 'After sending 6 arrow downx key to the heat slider, aria-valuenow should be "0" and aria-value-text should be: ' + + ex.heatValues[0] + ); } +); + +ariaTest( + 'page down decreases slider value by 10', + exampleFile, + 'key-page-down', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.PAGE_DOWN); - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin, - 'After sending 5 page down key, the value of the temp slider should be: ' + ex.tempMin - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMin, - 'Temp display should match value of slider: ' + ex.tempMin - ); -}); - -ariaTest('home set slider value to minimum', exampleFile, 'key-home', async (t) => { - - // Send key home to temp slider - const tempSlider = await t.context.session.findElement(By.css(ex.tempSelector)); - await tempSlider.sendKeys(Key.HOME); - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin, - 'After sending key HOME, the value of the temp slider should be: ' + ex.tempMin - ); - t.is( - await t.context.session.findElement(By.css(ex.pageTempSelector)).getText(), - ex.tempMin, - 'Temp display should match value of slider: ' + ex.tempMin - ); - - // Send key home to fan slider - const fanSlider = await t.context.session.findElement(By.css(ex.fanSelector)); - await fanSlider.sendKeys(Key.HOME); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMin, ex.fanValues[parseInt(ex.fanMin)]], - 'After sending key home to the heat slider, aria-valuenow should be "' + ex.fanMin + '" and aria-value-text should be: ' + ex.fanValues[parseInt(ex.fanMin)] - ); - - // Send key home to heat slider - const heatSlider = await t.context.session.findElement(By.css(ex.heatSelector)); - await heatSlider.sendKeys(Key.HOME); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMin, ex.heatValues[parseInt(ex.heatMin)]], - 'After sending key home to the heat slider, aria-valuenow should be "' + ex.heatMin + '" and aria-value-text should be: ' + ex.heatValues[parseInt(ex.heatMin)] - ); - -}); + let sliderVal = parseInt(ex.tempDefault) - 10; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 page down key to the temp slider, the value of the temp slider should be: ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + sliderVal.toString(), + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send more than 5 keys to temp slider + for (let i = 0; i < 5; i++) { + await tempSlider.sendKeys(Key.PAGE_DOWN); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin, + 'After sending 5 page down key, the value of the temp slider should be: ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMin, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); + +ariaTest( + 'home set slider value to minimum', + exampleFile, + 'key-home', + async (t) => { + // Send key home to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.HOME); + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin, + 'After sending key HOME, the value of the temp slider should be: ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.pageTempSelector)) + .getText(), + ex.tempMin, + 'Temp display should match value of slider: ' + ex.tempMin + ); + + // Send key home to fan slider + const fanSlider = await t.context.session.findElement( + By.css(ex.fanSelector) + ); + await fanSlider.sendKeys(Key.HOME); + + t.deepEqual( + await getValueAndText(t, ex.fanSelector), + [ex.fanMin, ex.fanValues[parseInt(ex.fanMin)]], + 'After sending key home to the heat slider, aria-valuenow should be "' + + ex.fanMin + + '" and aria-value-text should be: ' + + ex.fanValues[parseInt(ex.fanMin)] + ); + + // Send key home to heat slider + const heatSlider = await t.context.session.findElement( + By.css(ex.heatSelector) + ); + await heatSlider.sendKeys(Key.HOME); + + t.deepEqual( + await getValueAndText(t, ex.heatSelector), + [ex.heatMin, ex.heatValues[parseInt(ex.heatMin)]], + 'After sending key home to the heat slider, aria-valuenow should be "' + + ex.heatMin + + '" and aria-value-text should be: ' + + ex.heatValues[parseInt(ex.heatMin)] + ); + } +); diff --git a/test/tests/spinbutton_datepicker.js b/test/tests/spinbutton_datepicker.js index ef9d17c849..6f350a0a43 100644 --- a/test/tests/spinbutton_datepicker.js +++ b/test/tests/spinbutton_datepicker.js @@ -7,9 +7,54 @@ const assertAriaRoles = require('../util/assertAriaRoles'); const exampleFile = 'spinbutton/datepicker-spinbuttons.html'; -const valuesDay = ['', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteen', 'fifteenth', 'sixteenth', 'seveneenth', 'eighteenth', 'nineteenth', 'twentieth', 'twenty first', 'twenty second', 'twenty third', 'twenty fourth', 'twenty fifth', 'twenty sixth', 'twenty seventh', 'twenty eighth', 'twenty ninth', 'thirtieth', 'thirty first']; -const valuesMonth = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - +const valuesDay = [ + '', + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'sixth', + 'seventh', + 'eighth', + 'ninth', + 'tenth', + 'eleventh', + 'twelfth', + 'thirteenth', + 'fourteen', + 'fifteenth', + 'sixteenth', + 'seveneenth', + 'eighteenth', + 'nineteenth', + 'twentieth', + 'twenty first', + 'twenty second', + 'twenty third', + 'twenty fourth', + 'twenty fifth', + 'twenty sixth', + 'twenty seventh', + 'twenty eighth', + 'twenty ninth', + 'thirtieth', + 'thirty first', +]; +const valuesMonth = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +]; var getDaysInMonth = function (year, month) { return new Date(year, month + 1, 0).getDate(); @@ -52,86 +97,236 @@ const ex = { dayIncreaseSelector: '#example .spinbutton.day .increase', dayDecreaseSelector: '#example .spinbutton.day .decrease', dayHiddenPreviousSelector: '#example .spinbutton.day .previous', - dayHiddenNextSelector: '#example .spinbutton.day .next' + dayHiddenNextSelector: '#example .spinbutton.day .next', }; // Attributes -ariaTest('role="group" on div element', exampleFile, 'group-role', async (t) => { +ariaTest( + 'role="group" on div element', + exampleFile, + 'group-role', + async (t) => { await assertAriaRoles(t, 'example', 'group', '1', 'div'); -}); - -ariaTest('"aria-labelledby" attribute on group', exampleFile, 'group-aria-labelledby', async (t) => { - await assertAriaLabelledby(t, ex.groupSelector); -}); - -ariaTest('role="spinbutton" on div element', exampleFile, 'spinbutton-role', async (t) => { + } +); + +ariaTest( + '"aria-labelledby" attribute on group', + exampleFile, + 'group-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.groupSelector); + } +); + +ariaTest( + 'role="spinbutton" on div element', + exampleFile, + 'spinbutton-role', + async (t) => { await assertAriaRoles(t, 'example', 'spinbutton', '3', 'div'); -}); - -ariaTest('"aria-valuemax" represents the minimum value on spinbuttons', exampleFile, 'spinbutton-aria-valuemax', async (t) => { + } +); + +ariaTest( + '"aria-valuemax" represents the minimum value on spinbuttons', + exampleFile, + 'spinbutton-aria-valuemax', + async (t) => { await assertAttributeValues(t, ex.daySelector, 'aria-valuemax', ex.dayMax); - await assertAttributeValues(t, ex.monthSelector, 'aria-valuemax', ex.monthMax); - await assertAttributeValues(t, ex.yearSelector, 'aria-valuemax', ex.yearMax); -}); - -ariaTest('"aria-valuemin" represents the maximum value on spinbuttons', exampleFile, 'spinbutton-aria-valuemin', async (t) => { + await assertAttributeValues( + t, + ex.monthSelector, + 'aria-valuemax', + ex.monthMax + ); + await assertAttributeValues( + t, + ex.yearSelector, + 'aria-valuemax', + ex.yearMax + ); + } +); + +ariaTest( + '"aria-valuemin" represents the maximum value on spinbuttons', + exampleFile, + 'spinbutton-aria-valuemin', + async (t) => { await assertAttributeValues(t, ex.daySelector, 'aria-valuemin', ex.dayMin); - await assertAttributeValues(t, ex.monthSelector, 'aria-valuemin', ex.monthMin); - await assertAttributeValues(t, ex.yearSelector, 'aria-valuemin', ex.yearMin); -}); - -ariaTest('"aria-valuenow" reflects spinbutton value as a number', exampleFile, 'spinbutton-aria-valuenow', async (t) => { + await assertAttributeValues( + t, + ex.monthSelector, + 'aria-valuemin', + ex.monthMin + ); + await assertAttributeValues( + t, + ex.yearSelector, + 'aria-valuemin', + ex.yearMin + ); + } +); + +ariaTest( + '"aria-valuenow" reflects spinbutton value as a number', + exampleFile, + 'spinbutton-aria-valuenow', + async (t) => { await assertAttributeValues(t, ex.daySelector, 'aria-valuenow', ex.dayNow); - await assertAttributeValues(t, ex.monthSelector, 'aria-valuenow', ex.monthNow); - await assertAttributeValues(t, ex.yearSelector, 'aria-valuenow', ex.yearNow); -}); - - -ariaTest('"aria-valuetext" reflects spin button value as a text string', exampleFile, 'spinbutton-aria-valuetext', async (t) => { - await assertAttributeValues(t, ex.daySelector, 'aria-valuetext', ex.dayText); - await assertAttributeValues(t, ex.monthSelector, 'aria-valuetext', ex.monthText); -}); - -ariaTest('"aria-label" provides accessible name for the spin buttons to screen reader users', exampleFile, 'spinbutton-aria-label', async (t) => { - - await assertAttributeValues(t, ex.daySelector, 'aria-label', 'Day'); - await assertAttributeValues(t, ex.monthSelector, 'aria-label', 'Month'); - await assertAttributeValues(t, ex.yearSelector, 'aria-label', 'Year'); - -}); - -ariaTest('"tabindex=-1" removes previous and next from the tab order of the page', exampleFile, 'button-tabindex', async (t) => { + await assertAttributeValues( + t, + ex.monthSelector, + 'aria-valuenow', + ex.monthNow + ); + await assertAttributeValues( + t, + ex.yearSelector, + 'aria-valuenow', + ex.yearNow + ); + } +); + +ariaTest( + '"aria-valuetext" reflects spin button value as a text string', + exampleFile, + 'spinbutton-aria-valuetext', + async (t) => { + await assertAttributeValues( + t, + ex.daySelector, + 'aria-valuetext', + ex.dayText + ); + await assertAttributeValues( + t, + ex.monthSelector, + 'aria-valuetext', + ex.monthText + ); + } +); + +ariaTest( + '"aria-label" provides accessible name for the spin buttons to screen reader users', + exampleFile, + 'spinbutton-aria-label', + async (t) => { + await assertAttributeValues(t, ex.daySelector, 'aria-label', 'Day'); + await assertAttributeValues(t, ex.monthSelector, 'aria-label', 'Month'); + await assertAttributeValues(t, ex.yearSelector, 'aria-label', 'Year'); + } +); + +ariaTest( + '"tabindex=-1" removes previous and next from the tab order of the page', + exampleFile, + 'button-tabindex', + async (t) => { await assertAttributeValues(t, ex.dayIncreaseSelector, 'tabindex', '-1'); - await assertAttributeValues(t, ex.dayDecreaseSelector, 'tabindex', '-1'); - - await assertAttributeValues(t, ex.monthIncreaseSelector, 'tabindex', '-1'); - await assertAttributeValues(t, ex.monthDecreaseSelector, 'tabindex', '-1'); - - await assertAttributeValues(t, ex.yearIncreaseSelector, 'tabindex', '-1'); - await assertAttributeValues(t, ex.yearDecreaseSelector, 'tabindex', '-1'); -}); - -ariaTest('"aria-label" provides accessible name for the previous and next buttons to screen reader users', exampleFile, 'button-aria-label', async (t) => { - await assertAttributeValues(t, ex.dayIncreaseSelector, 'aria-label', 'next day'); - await assertAttributeValues(t, ex.dayDecreaseSelector, 'aria-label', 'previous day'); - - await assertAttributeValues(t, ex.monthIncreaseSelector, 'aria-label', 'next month'); - await assertAttributeValues(t, ex.monthDecreaseSelector, 'aria-label', 'previous month'); - - await assertAttributeValues(t, ex.yearIncreaseSelector, 'aria-label', 'next year'); - await assertAttributeValues(t, ex.yearDecreaseSelector, 'aria-label', 'previous year'); -}); - -ariaTest('"aria-hidden" hides decorative and redundant content form screen reader users', exampleFile, 'spinbutton-aria-hidden', async (t) => { - await assertAttributeValues(t, ex.dayHiddenPreviousSelector, 'aria-hidden', 'true'); - await assertAttributeValues(t, ex.dayHiddenNextSelector, 'aria-hidden', 'true'); - - await assertAttributeValues(t, ex.monthHiddenPreviousSelector, 'aria-hidden', 'true'); - await assertAttributeValues(t, ex.monthHiddenNextSelector, 'aria-hidden', 'true'); - - await assertAttributeValues(t, ex.yearHiddenPreviousSelector, 'aria-hidden', 'true'); - await assertAttributeValues(t, ex.yearHiddenNextSelector, 'aria-hidden', 'true'); -}); - - + await assertAttributeValues(t, ex.dayDecreaseSelector, 'tabindex', '-1'); + + await assertAttributeValues(t, ex.monthIncreaseSelector, 'tabindex', '-1'); + await assertAttributeValues(t, ex.monthDecreaseSelector, 'tabindex', '-1'); + + await assertAttributeValues(t, ex.yearIncreaseSelector, 'tabindex', '-1'); + await assertAttributeValues(t, ex.yearDecreaseSelector, 'tabindex', '-1'); + } +); + +ariaTest( + '"aria-label" provides accessible name for the previous and next buttons to screen reader users', + exampleFile, + 'button-aria-label', + async (t) => { + await assertAttributeValues( + t, + ex.dayIncreaseSelector, + 'aria-label', + 'next day' + ); + await assertAttributeValues( + t, + ex.dayDecreaseSelector, + 'aria-label', + 'previous day' + ); + + await assertAttributeValues( + t, + ex.monthIncreaseSelector, + 'aria-label', + 'next month' + ); + await assertAttributeValues( + t, + ex.monthDecreaseSelector, + 'aria-label', + 'previous month' + ); + + await assertAttributeValues( + t, + ex.yearIncreaseSelector, + 'aria-label', + 'next year' + ); + await assertAttributeValues( + t, + ex.yearDecreaseSelector, + 'aria-label', + 'previous year' + ); + } +); + +ariaTest( + '"aria-hidden" hides decorative and redundant content form screen reader users', + exampleFile, + 'spinbutton-aria-hidden', + async (t) => { + await assertAttributeValues( + t, + ex.dayHiddenPreviousSelector, + 'aria-hidden', + 'true' + ); + await assertAttributeValues( + t, + ex.dayHiddenNextSelector, + 'aria-hidden', + 'true' + ); + + await assertAttributeValues( + t, + ex.monthHiddenPreviousSelector, + 'aria-hidden', + 'true' + ); + await assertAttributeValues( + t, + ex.monthHiddenNextSelector, + 'aria-hidden', + 'true' + ); + + await assertAttributeValues( + t, + ex.yearHiddenPreviousSelector, + 'aria-hidden', + 'true' + ); + await assertAttributeValues( + t, + ex.yearHiddenNextSelector, + 'aria-hidden', + 'true' + ); + } +); diff --git a/test/tests/table_table.js b/test/tests/table_table.js index afb56bedca..300d9c1a3a 100644 --- a/test/tests/table_table.js +++ b/test/tests/table_table.js @@ -12,67 +12,95 @@ const ex = { numRowgroups: 2, numRows: 5, numColumnheaders: 4, - numCells: 16 + numCells: 16, }; // Attributes -ariaTest('role="table" element exists', exampleFile, 'table-role', async (t) => { +ariaTest( + 'role="table" element exists', + exampleFile, + 'table-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'table', 1, 'div'); -}); - -ariaTest('"aria-label" attribute on table element', exampleFile, 'table-aria-label', async (t) => { + } +); + +ariaTest( + '"aria-label" attribute on table element', + exampleFile, + 'table-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.tableSelector); -}); - -ariaTest('"aria-describedby" attribute on table element', exampleFile, 'table-aria-describedby', async (t) => { + } +); + +ariaTest( + '"aria-describedby" attribute on table element', + exampleFile, + 'table-aria-describedby', + async (t) => { await assertAriaDescribedby(t, ex.tableSelector); -}); - + } +); ariaTest('role="rowgroup" exists', exampleFile, 'rowgroup-role', async (t) => { - - const rowgroups = await t.context.queryElements(t, ex.tableSelector + ' [role="rowgroup"]'); + const rowgroups = await t.context.queryElements( + t, + ex.tableSelector + ' [role="rowgroup"]' + ); t.is( rowgroups.length, ex.numRowgroups, - ex.numRowgroups + ' role="rowgroup" elements should be found within the table element' + ex.numRowgroups + + ' role="rowgroup" elements should be found within the table element' ); }); - ariaTest('role="row" exists', exampleFile, 'row-role', async (t) => { - - const rows = await t.context.queryElements(t, ex.tableSelector + ' [role="rowgroup"] [role="row"]'); + const rows = await t.context.queryElements( + t, + ex.tableSelector + ' [role="rowgroup"] [role="row"]' + ); t.is( rows.length, ex.numRows, - ex.numRows + ' role="row" elements should be found nested within the table element and rowgroup elements' + ex.numRows + + ' role="row" elements should be found nested within the table element and rowgroup elements' ); }); - -ariaTest('role="columnheader" exists', exampleFile, 'columnheader-role', async (t) => { - - const columnheaders = await t.context.queryElements(t, ex.tableSelector + ' [role="rowgroup"] [role="row"] [role="columnheader"]'); - - t.is( - columnheaders.length, - ex.numColumnheaders, - ex.numColumnheaders + ' role="columnheader" elements should be found nested within the table element, rowgroup element and row element.' - ); - -}); +ariaTest( + 'role="columnheader" exists', + exampleFile, + 'columnheader-role', + async (t) => { + const columnheaders = await t.context.queryElements( + t, + ex.tableSelector + ' [role="rowgroup"] [role="row"] [role="columnheader"]' + ); + + t.is( + columnheaders.length, + ex.numColumnheaders, + ex.numColumnheaders + + ' role="columnheader" elements should be found nested within the table element, rowgroup element and row element.' + ); + } +); ariaTest('role="cell" exists', exampleFile, 'cell-role', async (t) => { - - const cells = await t.context.queryElements(t, ex.tableSelector + ' [role="rowgroup"] [role="row"] [role="cell"]'); + const cells = await t.context.queryElements( + t, + ex.tableSelector + ' [role="rowgroup"] [role="row"] [role="cell"]' + ); t.is( cells.length, ex.numCells, - ex.numCells + ' role="cell" elements should be found nested within the table element, rowgroup element and row element.' + ex.numCells + + ' role="cell" elements should be found nested within the table element, rowgroup element and row element.' ); }); diff --git a/test/tests/tabs_tabs-1.js b/test/tests/tabs_tabs-1.js index 81ba95835b..85cd7b126b 100644 --- a/test/tests/tabs_tabs-1.js +++ b/test/tests/tabs_tabs-1.js @@ -22,8 +22,8 @@ const ex = { // button id, tab id ['#nils', '#nils-tab'], ['#agnes', '#agnes-tab'], - ['#complex', '#complexcomplex'] - ] + ['#complex', '#complexcomplex'], + ], }; const openTabAtIndex = async function (t, index) { @@ -32,90 +32,134 @@ const openTabAtIndex = async function (t, index) { }; const waitAndCheckFocus = async function (t, selector, index) { - return t.context.session.wait(async function () { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - let items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); - }, t.context.waitTime, 'Timeout waiting for document.activeElement to become item at index ' + index + ' of elements selected by: ' + selector); + return t.context.session.wait( + async function () { + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + let items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); + }, + t.context.waitTime, + 'Timeout waiting for document.activeElement to become item at index ' + + index + + ' of elements selected by: ' + + selector + ); }; const waitAndCheckAriaSelected = async function (t, index) { - return t.context.session.wait(async function () { - const tabs = await t.context.queryElements(t, ex.tabSelector); - return (await tabs[index].getAttribute('aria-selected')) === 'true'; - }, t.context.waitTime, 'Timeout waiting for aria-selected to be set to true.'); + return t.context.session.wait( + async function () { + const tabs = await t.context.queryElements(t, ex.tabSelector); + return (await tabs[index].getAttribute('aria-selected')) === 'true'; + }, + t.context.waitTime, + 'Timeout waiting for aria-selected to be set to true.' + ); }; // Attributes -ariaTest('role="tablist" on div element', exampleFile, 'tablist-role', async (t) => { +ariaTest( + 'role="tablist" on div element', + exampleFile, + 'tablist-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'tablist', '1', 'div'); -}); + } +); -ariaTest('"ariaLabel" attribute on role="tablist"', exampleFile, 'tablist-aria-label', async (t) => { +ariaTest( + '"ariaLabel" attribute on role="tablist"', + exampleFile, + 'tablist-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.tablistSelector); -}); + } +); -ariaTest('role="tab" on button elements', exampleFile, 'tab-role', async (t) => { +ariaTest( + 'role="tab" on button elements', + exampleFile, + 'tab-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'tab', '3', 'button'); -}); - -ariaTest('"aria-selected" set on role="tab"', exampleFile, 'tab-aria-selected', async (t) => { - - let tabs = await t.context.queryElements(t, ex.tabSelector); - let tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - - for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { - - // Open the tab - await openTabAtIndex(t, selectedEl); - - for (let el = 0; el < tabs.length; el++) { - - // test only one element has aria-selected="true" - let selected = el === selectedEl ? 'true' : 'false'; - t.is( - await tabs[el].getAttribute('aria-selected'), - selected, - 'Tab at index ' + selectedEl + ' is selected, therefore, tab at index ' + - el + ' should have aria-selected="' + selected + '"' - ); + } +); + +ariaTest( + '"aria-selected" set on role="tab"', + exampleFile, + 'tab-aria-selected', + async (t) => { + let tabs = await t.context.queryElements(t, ex.tabSelector); + let tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + + for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { + // Open the tab + await openTabAtIndex(t, selectedEl); + + for (let el = 0; el < tabs.length; el++) { + // test only one element has aria-selected="true" + let selected = el === selectedEl ? 'true' : 'false'; + t.is( + await tabs[el].getAttribute('aria-selected'), + selected, + 'Tab at index ' + + selectedEl + + ' is selected, therefore, tab at index ' + + el + + ' should have aria-selected="' + + selected + + '"' + ); - // test only the appropriate tabpanel element is visible - let tabpanelVisible = el === selectedEl; - t.is( - await tabpanels[el].isDisplayed(), - tabpanelVisible, - 'Tab at index ' + selectedEl + ' is selected, therefore, only the tabpanel at ' + - 'index ' + selectedEl + ' should be displayed' - ); + // test only the appropriate tabpanel element is visible + let tabpanelVisible = el === selectedEl; + t.is( + await tabpanels[el].isDisplayed(), + tabpanelVisible, + 'Tab at index ' + + selectedEl + + ' is selected, therefore, only the tabpanel at ' + + 'index ' + + selectedEl + + ' should be displayed' + ); + } } } -}); +); ariaTest('"tabindex" on role="tab"', exampleFile, 'tab-tabindex', async (t) => { - let tabs = await t.context.queryElements(t, ex.tabSelector); for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { - // Open the tab await openTabAtIndex(t, selectedEl); for (let el = 0; el < tabs.length; el++) { - // The open tab should have no tabindex set if (el === selectedEl) { - const tabindexExists = await t.context.session.executeScript(async function () { - const [selector, el] = arguments; - let tabs = document.querySelectorAll(selector); - return tabs[el].hasAttribute('tabindex'); - }, ex.tabSelector, el); + const tabindexExists = await t.context.session.executeScript( + async function () { + const [selector, el] = arguments; + let tabs = document.querySelectorAll(selector); + return tabs[el].hasAttribute('tabindex'); + }, + ex.tabSelector, + el + ); t.false( tabindexExists, - 'Tab at index ' + selectedEl + ' is selected, therefore, that tab should not ' + + 'Tab at index ' + + selectedEl + + ' is selected, therefore, that tab should not ' + 'have the "tabindex" attribute' ); } @@ -125,232 +169,294 @@ ariaTest('"tabindex" on role="tab"', exampleFile, 'tab-tabindex', async (t) => { t.is( await tabs[el].getAttribute('tabindex'), '-1', - 'Tab at index ' + selectedEl + ' is selected, therefore, tab at index ' + - el + ' should have tabindex="-1"' + 'Tab at index ' + + selectedEl + + ' is selected, therefore, tab at index ' + + el + + ' should have tabindex="-1"' ); } } } }); -ariaTest('"aria-control" attribute on role="tab"', exampleFile, 'tab-aria-control', async (t) => { +ariaTest( + '"aria-control" attribute on role="tab"', + exampleFile, + 'tab-aria-control', + async (t) => { await assertAriaControls(t, ex.tabSelector); -}); + } +); -ariaTest('role="tabpanel" on div element', exampleFile, 'tabpanel-role', async (t) => { +ariaTest( + 'role="tabpanel" on div element', + exampleFile, + 'tabpanel-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'tabpanel', '3', 'div'); -}); + } +); -ariaTest('"aria-labelledby" attribute on role="tabpanel" elements', exampleFile, 'tabpanel-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" attribute on role="tabpanel" elements', + exampleFile, + 'tabpanel-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.tabpanelSelector); -}); + } +); -ariaTest('tabindex="0" on role="tabpanel" elements', exampleFile, 'tabpanel-tabindex', async (t) => { +ariaTest( + 'tabindex="0" on role="tabpanel" elements', + exampleFile, + 'tabpanel-tabindex', + async (t) => { await assertAttributeValues(t, ex.tabpanelSelector, 'tabindex', '0'); -}); - + } +); // Keys -ariaTest('TAB key moves focus to open tab and panel', exampleFile, 'key-tab', async (t) => { - - for (let index = 0; index < ex.tabCount; index++) { - await openTabAtIndex(t, index); +ariaTest( + 'TAB key moves focus to open tab and panel', + exampleFile, + 'key-tab', + async (t) => { + for (let index = 0; index < ex.tabCount; index++) { + await openTabAtIndex(t, index); - await assertTabOrder(t, ex.tabTabOrder[index]); + await assertTabOrder(t, ex.tabTabOrder[index]); + } } -}); +); -ariaTest('ARROW_RIGHT key moves focus and activates tab', exampleFile, 'key-right-arrow', async (t) => { +ariaTest( + 'ARROW_RIGHT key moves focus and activates tab', + exampleFile, + 'key-right-arrow', + async (t) => { + // Put focus on first tab + await openTabAtIndex(t, 0); - // Put focus on first tab - await openTabAtIndex(t, 0); + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length - 1; index++) { + // Send the arrow right key to move focus + await tabs[index].sendKeys(Key.ARROW_RIGHT); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, index + 1), + 'right arrow on tab "' + index + '" should put focus on the next tab.' + ); - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length - 1; index++) { + t.true( + await waitAndCheckAriaSelected(t, index + 1), + 'right arrow on tab "' + + index + + '" should set aria-selected="true" on next tab.' + ); + t.true( + await tabpanels[index + 1].isDisplayed(), + 'right arrow on tab "' + index + '" should display the next tab panel.' + ); + } // Send the arrow right key to move focus - await tabs[index].sendKeys(Key.ARROW_RIGHT); - - // Check the focus is correct - t.true( - await waitAndCheckFocus(t, ex.tabSelector, index + 1), - 'right arrow on tab "' + index + '" should put focus on the next tab.' - ); + await tabs[tabs.length - 1].sendKeys(Key.ARROW_RIGHT); - t.true( - await waitAndCheckAriaSelected(t, index + 1), - 'right arrow on tab "' + index + '" should set aria-selected="true" on next tab.' - ); - t.true( - await tabpanels[index + 1].isDisplayed(), - 'right arrow on tab "' + index + '" should display the next tab panel.' - ); - } - - // Send the arrow right key to move focus - await tabs[tabs.length - 1].sendKeys(Key.ARROW_RIGHT); - - // Check the focus returns to the first item - t.true( - await waitAndCheckFocus(t, ex.tabSelector, 0), - 'right arrow on tab "' + (tabs.length - 1) + '" should put focus to first tab.' - ); - t.true( - await waitAndCheckAriaSelected(t, 0), - 'right arrow on tab "' + (tabs.length - 1) + '" should set aria-selected="true" on first tab.' - ); - t.true( - await tabpanels[0].isDisplayed(), - 'right arrow on tab "' + (tabs.length - 1) + '" should display the first panel.' - ); - -}); - -ariaTest('ARROW_LEFT key moves focus and activates tab', exampleFile, 'key-left-arrow', async (t) => { - - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - - // Put focus on first tab - await openTabAtIndex(t, 0); - - // Send the left arrow - await tabs[0].sendKeys(Key.ARROW_LEFT); - - // Check the focus returns to the last item - t.true( - await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), - 'left arrow on tab 0 should put focus to last tab.' - ); - - t.true( - await waitAndCheckAriaSelected(t, tabs.length - 1), - 'left arrow on tab 0 should set aria-selected="true" on last tab.' - ); - t.true( - await tabpanels[tabs.length - 1].isDisplayed(), - 'left arrow on tab 0 should display the last panel.' - ); - - for (let index = tabs.length - 1; index > 0; index--) { - - // Send the arrow left key to move focus - await tabs[index].sendKeys(Key.ARROW_LEFT); - - // Check the focus is correct - t.true( - await waitAndCheckFocus(t, ex.tabSelector, index - 1), - 'left arrow on tab "' + index + '" should put focus on the previous tab.' - ); - t.true( - await waitAndCheckAriaSelected(t, index - 1), - 'left arrow on tab "' + index + '" should set aria-selected="true" on previous tab.' - ); - t.true( - await tabpanels[index - 1].isDisplayed(), - 'left arrow on tab "' + index + '" should display the next previous panel.' - ); - } -}); - -ariaTest('HOME key moves focus and selects tab', exampleFile, 'key-home', async (t) => { - - - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length; index++) { - - // Put focus on the tab - await openTabAtIndex(t, index); - - // Send the home key to the tab - await tabs[index].sendKeys(Key.HOME); - - // Check the focus is correct + // Check the focus returns to the first item t.true( await waitAndCheckFocus(t, ex.tabSelector, 0), - 'home key on tab "' + index + '" should put focus on the first tab.' + 'right arrow on tab "' + + (tabs.length - 1) + + '" should put focus to first tab.' ); t.true( await waitAndCheckAriaSelected(t, 0), - 'home key on tab "' + index + '" should set aria-selected="true" on the first tab.' + 'right arrow on tab "' + + (tabs.length - 1) + + '" should set aria-selected="true" on first tab.' ); t.true( await tabpanels[0].isDisplayed(), - 'home key on tab "' + index + '" should display the first tab.' + 'right arrow on tab "' + + (tabs.length - 1) + + '" should display the first panel.' ); } -}); +); -ariaTest('END key moves focus and selects tab', exampleFile, 'key-end', async (t) => { - - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length; index++) { +ariaTest( + 'ARROW_LEFT key moves focus and activates tab', + exampleFile, + 'key-left-arrow', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - // Put focus on the tab - await openTabAtIndex(t, index); + // Put focus on first tab + await openTabAtIndex(t, 0); - // Send the end key to the tab - await tabs[index].sendKeys(Key.END); + // Send the left arrow + await tabs[0].sendKeys(Key.ARROW_LEFT); - // Check the focus is correct + // Check the focus returns to the last item t.true( await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), - 'home key on tab "' + index + '" should put focus on the last tab.' + 'left arrow on tab 0 should put focus to last tab.' ); + t.true( await waitAndCheckAriaSelected(t, tabs.length - 1), - 'home key on tab "' + index + '" should set aria-selected="true" on the last tab.' + 'left arrow on tab 0 should set aria-selected="true" on last tab.' ); t.true( await tabpanels[tabs.length - 1].isDisplayed(), - 'home key on tab "' + index + '" should display the last tab.' + 'left arrow on tab 0 should display the last panel.' ); + + for (let index = tabs.length - 1; index > 0; index--) { + // Send the arrow left key to move focus + await tabs[index].sendKeys(Key.ARROW_LEFT); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, index - 1), + 'left arrow on tab "' + + index + + '" should put focus on the previous tab.' + ); + t.true( + await waitAndCheckAriaSelected(t, index - 1), + 'left arrow on tab "' + + index + + '" should set aria-selected="true" on previous tab.' + ); + t.true( + await tabpanels[index - 1].isDisplayed(), + 'left arrow on tab "' + + index + + '" should display the next previous panel.' + ); + } } -}); +); -ariaTest('DELETE key removes third tab', exampleFile, 'key-delete', async (t) => { +ariaTest( + 'HOME key moves focus and selects tab', + exampleFile, + 'key-home', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length; index++) { + // Put focus on the tab + await openTabAtIndex(t, index); + + // Send the home key to the tab + await tabs[index].sendKeys(Key.HOME); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, 0), + 'home key on tab "' + index + '" should put focus on the first tab.' + ); + t.true( + await waitAndCheckAriaSelected(t, 0), + 'home key on tab "' + + index + + '" should set aria-selected="true" on the first tab.' + ); + t.true( + await tabpanels[0].isDisplayed(), + 'home key on tab "' + index + '" should display the first tab.' + ); + } + } +); - let tabs = await t.context.queryElements(t, ex.tabSelector); +ariaTest( + 'END key moves focus and selects tab', + exampleFile, + 'key-end', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length; index++) { + // Put focus on the tab + await openTabAtIndex(t, index); + + // Send the end key to the tab + await tabs[index].sendKeys(Key.END); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), + 'home key on tab "' + index + '" should put focus on the last tab.' + ); + t.true( + await waitAndCheckAriaSelected(t, tabs.length - 1), + 'home key on tab "' + + index + + '" should set aria-selected="true" on the last tab.' + ); + t.true( + await tabpanels[tabs.length - 1].isDisplayed(), + 'home key on tab "' + index + '" should display the last tab.' + ); + } + } +); - // Put focus on the first tab - await openTabAtIndex(t, 0); +ariaTest( + 'DELETE key removes third tab', + exampleFile, + 'key-delete', + async (t) => { + let tabs = await t.context.queryElements(t, ex.tabSelector); - // Send the delete key to the tab - await tabs[0].sendKeys(Key.DELETE); + // Put focus on the first tab + await openTabAtIndex(t, 0); - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to first tab should not change number of tabs' - ); + // Send the delete key to the tab + await tabs[0].sendKeys(Key.DELETE); + + t.is( + (await t.context.queryElements(t, ex.tabSelector)).length, + 3, + 'Sending DELETE to first tab should not change number of tabs' + ); - // Put focus on the second tab - await openTabAtIndex(t, 1); + // Put focus on the second tab + await openTabAtIndex(t, 1); - // Send the delete key to the tab - await tabs[1].sendKeys(Key.DELETE); + // Send the delete key to the tab + await tabs[1].sendKeys(Key.DELETE); - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to second tab should not change number of tabs' - ); + t.is( + (await t.context.queryElements(t, ex.tabSelector)).length, + 3, + 'Sending DELETE to second tab should not change number of tabs' + ); - // Put focus on the last tab - await openTabAtIndex(t, 2); + // Put focus on the last tab + await openTabAtIndex(t, 2); - // Send the delete key to the tab - await tabs[2].sendKeys(Key.DELETE); + // Send the delete key to the tab + await tabs[2].sendKeys(Key.DELETE); - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 2, - 'Sending DELETE to third tab should change number of tabs' - ); + t.is( + (await t.context.queryElements(t, ex.tabSelector)).length, + 2, + 'Sending DELETE to third tab should change number of tabs' + ); - assertNoElements(t, `#${ex.deletableId}`, `Sending DELETE to third tab should have delete tab with id: ${ex.deletableId}`); -}); + assertNoElements( + t, + `#${ex.deletableId}`, + `Sending DELETE to third tab should have delete tab with id: ${ex.deletableId}` + ); + } +); diff --git a/test/tests/tabs_tabs-2.js b/test/tests/tabs_tabs-2.js index 0eda416d60..0db668c9ca 100644 --- a/test/tests/tabs_tabs-2.js +++ b/test/tests/tabs_tabs-2.js @@ -22,8 +22,8 @@ const ex = { // button id, tab id ['#nils', '#nils-tab'], ['#agnes', '#agnes-tab'], - ['#complex', '#complexcomplex'] - ] + ['#complex', '#complexcomplex'], + ], }; const openTabAtIndex = async function (t, tabOrderIndex) { @@ -33,93 +33,136 @@ const openTabAtIndex = async function (t, tabOrderIndex) { }; const waitAndCheckFocus = async function (t, selector, index) { - return t.context.session.wait(async function () { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); - }, t.context.waitTime, 'Timeout waiting for document.activeElement to become item at index ' + index + ' of elements selected by: ' + selector); + return t.context.session.wait( + async function () { + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); + }, + t.context.waitTime, + 'Timeout waiting for document.activeElement to become item at index ' + + index + + ' of elements selected by: ' + + selector + ); }; const waitAndCheckAriaSelected = async function (t, index) { - return t.context.session.wait(async function () { - const tabs = await t.context.queryElements(t, ex.tabSelector); - return (await tabs[index].getAttribute('aria-selected')) === 'true'; - }, t.context.waitTime, 'Timeout waiting for aria-selected to be set to true.'); + return t.context.session.wait( + async function () { + const tabs = await t.context.queryElements(t, ex.tabSelector); + return (await tabs[index].getAttribute('aria-selected')) === 'true'; + }, + t.context.waitTime, + 'Timeout waiting for aria-selected to be set to true.' + ); }; // Attributes -ariaTest('role="tablist" on div element', exampleFile, 'tablist-role', async (t) => { +ariaTest( + 'role="tablist" on div element', + exampleFile, + 'tablist-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'tablist', '1', 'div'); -}); + } +); -ariaTest('"ariaLabel" attribute on role="tablist"', exampleFile, 'tablist-aria-label', async (t) => { +ariaTest( + '"ariaLabel" attribute on role="tablist"', + exampleFile, + 'tablist-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.tablistSelector); -}); + } +); -ariaTest('role="tab" on button elements', exampleFile, 'tab-role', async (t) => { +ariaTest( + 'role="tab" on button elements', + exampleFile, + 'tab-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'tab', '3', 'button'); -}); - -ariaTest('"aria-selected" set on role="tab"', exampleFile, 'tab-aria-selected', async (t) => { - - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - - for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { + } +); - // Open the tab - await openTabAtIndex(t, selectedEl); +ariaTest( + '"aria-selected" set on role="tab"', + exampleFile, + 'tab-aria-selected', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let el = 0; el < tabs.length; el++) { + for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { + // Open the tab + await openTabAtIndex(t, selectedEl); - // test only one element has aria-selected="true" - let selected = el === selectedEl ? 'true' : 'false'; - t.is( - await tabs[el].getAttribute('aria-selected'), - selected, - 'Tab at index ' + selectedEl + ' is selected, therefore, tab at index ' + - el + ' should have aria-selected="' + selected + '"' - ); + for (let el = 0; el < tabs.length; el++) { + // test only one element has aria-selected="true" + let selected = el === selectedEl ? 'true' : 'false'; + t.is( + await tabs[el].getAttribute('aria-selected'), + selected, + 'Tab at index ' + + selectedEl + + ' is selected, therefore, tab at index ' + + el + + ' should have aria-selected="' + + selected + + '"' + ); - // test only the appropriate tabpanel element is visible - let tabpanelVisible = el === selectedEl; - t.is( - await tabpanels[el].isDisplayed(), - tabpanelVisible, - 'Tab at index ' + selectedEl + ' is selected, therefore, only the tabpanel at ' + - 'index ' + selectedEl + ' should be displayed' - ); + // test only the appropriate tabpanel element is visible + let tabpanelVisible = el === selectedEl; + t.is( + await tabpanels[el].isDisplayed(), + tabpanelVisible, + 'Tab at index ' + + selectedEl + + ' is selected, therefore, only the tabpanel at ' + + 'index ' + + selectedEl + + ' should be displayed' + ); + } } } -}); +); ariaTest('"tabindex" on role="tab"', exampleFile, 'tab-tabindex', async (t) => { - const tabs = await t.context.queryElements(t, ex.tabSelector); for (let selectedEl = 0; selectedEl < tabs.length; selectedEl++) { - // Open the tab await openTabAtIndex(t, selectedEl); for (let el = 0; el < tabs.length; el++) { - // The open tab should have no tabindex set if (el === selectedEl) { - const tabindexExists = await t.context.session.executeScript(async function () { - const [selector, el] = arguments; - let tabs = document.querySelectorAll(selector); - return tabs[el].hasAttribute('tabindex'); - }, ex.tabSelector, el); + const tabindexExists = await t.context.session.executeScript( + async function () { + const [selector, el] = arguments; + let tabs = document.querySelectorAll(selector); + return tabs[el].hasAttribute('tabindex'); + }, + ex.tabSelector, + el + ); t.false( tabindexExists, - 'Tab at index ' + selectedEl + ' is selected, therefore, that tab should not ' + + 'Tab at index ' + + selectedEl + + ' is selected, therefore, that tab should not ' + 'have the "tabindex" attribute' ); - } // Unopened tabs should have tabindex="-1" @@ -127,165 +170,205 @@ ariaTest('"tabindex" on role="tab"', exampleFile, 'tab-tabindex', async (t) => { t.is( await tabs[el].getAttribute('tabindex'), '-1', - 'Tab at index ' + selectedEl + ' is selected, therefore, tab at index ' + - el + ' should have tabindex="-1"' + 'Tab at index ' + + selectedEl + + ' is selected, therefore, tab at index ' + + el + + ' should have tabindex="-1"' ); } } } }); -ariaTest('"aria-control" attribute on role="tab"', exampleFile, 'tab-aria-controls', async (t) => { +ariaTest( + '"aria-control" attribute on role="tab"', + exampleFile, + 'tab-aria-controls', + async (t) => { await assertAriaControls(t, ex.tabSelector); -}); + } +); -ariaTest('role="tabpanel" on div element', exampleFile, 'tabpanel-role', async (t) => { +ariaTest( + 'role="tabpanel" on div element', + exampleFile, + 'tabpanel-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'tabpanel', '3', 'div'); -}); + } +); -ariaTest('"aria-labelledby" attribute on role="tabpanel" elements', exampleFile, 'tabpanel-aria-labelledby', async (t) => { +ariaTest( + '"aria-labelledby" attribute on role="tabpanel" elements', + exampleFile, + 'tabpanel-aria-labelledby', + async (t) => { await assertAriaLabelledby(t, ex.tabpanelSelector); -}); + } +); -ariaTest('tabindex="0" on role="tabpanel" elements', exampleFile, 'tabpanel-tabindex', async (t) => { +ariaTest( + 'tabindex="0" on role="tabpanel" elements', + exampleFile, + 'tabpanel-tabindex', + async (t) => { await assertAttributeValues(t, ex.tabpanelSelector, 'tabindex', '0'); -}); - + } +); // Keys -ariaTest('TAB key moves focus to open tab and panel', exampleFile, 'key-tab', async (t) => { - - for (let index = 0; index < ex.tabCount; index++) { - await openTabAtIndex(t, index); +ariaTest( + 'TAB key moves focus to open tab and panel', + exampleFile, + 'key-tab', + async (t) => { + for (let index = 0; index < ex.tabCount; index++) { + await openTabAtIndex(t, index); - await assertTabOrder(t, ex.tabTabOrder[index]); + await assertTabOrder(t, ex.tabTabOrder[index]); + } } -}); - -ariaTest('ARROW_RIGHT key moves focus', exampleFile, 'key-right-arrow', async (t) => { +); - // Put focus on first tab - await openTabAtIndex(t, 0); +ariaTest( + 'ARROW_RIGHT key moves focus', + exampleFile, + 'key-right-arrow', + async (t) => { + // Put focus on first tab + await openTabAtIndex(t, 0); - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length - 1; index++) { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length - 1; index++) { + // Send the arrow right key to move focus + await tabs[index].sendKeys(Key.ARROW_RIGHT); + + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, index + 1), + 'right arrow on tab "' + index + '" should put focus on the next tab.' + ); + t.false( + await tabpanels[index + 1].isDisplayed(), + 'right arrow on tab "' + + index + + '" should not display the next tab panel.' + ); + } // Send the arrow right key to move focus - await tabs[index].sendKeys(Key.ARROW_RIGHT); + await tabs[tabs.length - 1].sendKeys(Key.ARROW_RIGHT); - // Check the focus is correct + // Check the focus returns to the first item t.true( - await waitAndCheckFocus(t, ex.tabSelector, index + 1), - 'right arrow on tab "' + index + '" should put focus on the next tab.' - ); - t.false( - await tabpanels[index + 1].isDisplayed(), - 'right arrow on tab "' + index + '" should not display the next tab panel.' + await waitAndCheckFocus(t, ex.tabSelector, 0), + 'right arrow on tab "' + + (tabs.length - 1) + + '" should put focus to first tab.' ); } +); - // Send the arrow right key to move focus - await tabs[tabs.length - 1].sendKeys(Key.ARROW_RIGHT); - - // Check the focus returns to the first item - t.true( - await waitAndCheckFocus(t, ex.tabSelector, 0), - 'right arrow on tab "' + (tabs.length - 1) + '" should put focus to first tab.' - ); -}); - -ariaTest('ENTER activates tab that contains focus', exampleFile, 'key-enter-or-space', async (t) => { - - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length - 1; index++) { +ariaTest( + 'ENTER activates tab that contains focus', + exampleFile, + 'key-enter-or-space', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length - 1; index++) { + // Send ENTER to activate tab + await tabs[index].sendKeys(Key.ENTER); + + // Check the focus is correct + t.true( + await tabpanels[index].isDisplayed(), + 'Enter on tab "' + index + '" should active the current panel.' + ); + t.true( + await waitAndCheckAriaSelected(t, index), + 'Enter on tab "' + index + '" should set aria-selected to "true".' + ); - // Send ENTER to activate tab - await tabs[index].sendKeys(Key.ENTER); + // Send arrow key to move focus + await tabs[index].sendKeys(Key.ARROW_RIGHT); + } + } +); - // Check the focus is correct - t.true( - await tabpanels[index].isDisplayed(), - 'Enter on tab "' + index + '" should active the current panel.' - ); - t.true( - await waitAndCheckAriaSelected(t, index), - 'Enter on tab "' + index + '" should set aria-selected to "true".' - ); +ariaTest( + 'SPACE activates tab that contains focus', + exampleFile, + 'key-enter-or-space', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); + for (let index = 0; index < tabs.length - 1; index++) { + // Send SPACE to activate tab + await tabs[index].sendKeys(Key.SPACE); + + // Check the focus is correct + t.true( + await tabpanels[index].isDisplayed(), + 'Enter on tab "' + index + '" should active the current panel.' + ); + t.true( + await waitAndCheckAriaSelected(t, index), + 'Enter on tab "' + index + '" should set aria-selected to "true".' + ); - // Send arrow key to move focus - await tabs[index].sendKeys(Key.ARROW_RIGHT); + // Send arrow key to move focus + await tabs[index].sendKeys(Key.ARROW_RIGHT); + } } -}); +); -ariaTest('SPACE activates tab that contains focus', exampleFile, 'key-enter-or-space', async (t) => { +ariaTest( + 'ARROW_LEFT key moves focus', + exampleFile, + 'key-left-arrow', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); + const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - for (let index = 0; index < tabs.length - 1; index++) { + // Put focus on first tab + await openTabAtIndex(t, 0); - // Send SPACE to activate tab - await tabs[index].sendKeys(Key.SPACE); + // Send the right arrow + await tabs[0].sendKeys(Key.ARROW_LEFT); - // Check the focus is correct + // Check the focus returns to the last item t.true( - await tabpanels[index].isDisplayed(), - 'Enter on tab "' + index + '" should active the current panel.' + await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), + 'right arrow on tab 0 should put focus to last tab.' ); - t.true( - await waitAndCheckAriaSelected(t, index), - 'Enter on tab "' + index + '" should set aria-selected to "true".' + t.false( + await tabpanels[tabs.length - 1].isDisplayed(), + 'right arrow on tab 0 should not display the last panel.' ); - // Send arrow key to move focus - await tabs[index].sendKeys(Key.ARROW_RIGHT); - } -}); - - -ariaTest('ARROW_LEFT key moves focus', exampleFile, 'key-left-arrow', async (t) => { - - const tabs = await t.context.queryElements(t, ex.tabSelector); - const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); - - // Put focus on first tab - await openTabAtIndex(t, 0); - - // Send the right arrow - await tabs[0].sendKeys(Key.ARROW_LEFT); - - // Check the focus returns to the last item - t.true( - await waitAndCheckFocus(t, ex.tabSelector, tabs.length - 1), - 'right arrow on tab 0 should put focus to last tab.' - ); - t.false( - await tabpanels[tabs.length - 1].isDisplayed(), - 'right arrow on tab 0 should not display the last panel.' - ); - - for (let index = tabs.length - 1; index > 0; index--) { - - // Send the arrow right key to move focus - await tabs[index].sendKeys(Key.ARROW_LEFT); + for (let index = tabs.length - 1; index > 0; index--) { + // Send the arrow right key to move focus + await tabs[index].sendKeys(Key.ARROW_LEFT); - // Check the focus is correct - t.true( - await waitAndCheckFocus(t, ex.tabSelector, index - 1), - 'right arrow on tab "' + index + '" should put focus on the previous tab.' - ); + // Check the focus is correct + t.true( + await waitAndCheckFocus(t, ex.tabSelector, index - 1), + 'right arrow on tab "' + + index + + '" should put focus on the previous tab.' + ); + } } -}); +); ariaTest('HOME key moves focus', exampleFile, 'key-home', async (t) => { - - const tabs = await t.context.queryElements(t, ex.tabSelector); const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); for (let index = 0; index < tabs.length; index++) { - // Put focus on the tab await openTabAtIndex(t, index); @@ -301,11 +384,9 @@ ariaTest('HOME key moves focus', exampleFile, 'key-home', async (t) => { }); ariaTest('END key moves focus', exampleFile, 'key-end', async (t) => { - const tabs = await t.context.queryElements(t, ex.tabSelector); const tabpanels = await t.context.queryElements(t, ex.tabpanelSelector); for (let index = 0; index < tabs.length; index++) { - // Put focus on the tab await openTabAtIndex(t, index); @@ -320,45 +401,53 @@ ariaTest('END key moves focus', exampleFile, 'key-end', async (t) => { } }); -ariaTest('DELETE key removes third tab', exampleFile, 'key-delete', async (t) => { - - const tabs = await t.context.queryElements(t, ex.tabSelector); +ariaTest( + 'DELETE key removes third tab', + exampleFile, + 'key-delete', + async (t) => { + const tabs = await t.context.queryElements(t, ex.tabSelector); - // Put focus on the first tab - await openTabAtIndex(t, 0); + // Put focus on the first tab + await openTabAtIndex(t, 0); - // Send the delete key to the tab - await tabs[0].sendKeys(Key.DELETE); + // Send the delete key to the tab + await tabs[0].sendKeys(Key.DELETE); - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to first tab should not change number of tabs' - ); + t.is( + (await t.context.queryElements(t, ex.tabSelector)).length, + 3, + 'Sending DELETE to first tab should not change number of tabs' + ); - // Put focus on the second tab - await openTabAtIndex(t, 1); + // Put focus on the second tab + await openTabAtIndex(t, 1); - // Send the delete key to the tab - await tabs[1].sendKeys(Key.DELETE); + // Send the delete key to the tab + await tabs[1].sendKeys(Key.DELETE); - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to second tab should not change number of tabs' - ); + t.is( + (await t.context.queryElements(t, ex.tabSelector)).length, + 3, + 'Sending DELETE to second tab should not change number of tabs' + ); - // Put focus on the last tab - await openTabAtIndex(t, 2); + // Put focus on the last tab + await openTabAtIndex(t, 2); - // Send the delete key to the tab - await tabs[2].sendKeys(Key.DELETE); + // Send the delete key to the tab + await tabs[2].sendKeys(Key.DELETE); - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 2, - 'Sending DELETE to third tab should change number of tabs' - ); + t.is( + (await t.context.queryElements(t, ex.tabSelector)).length, + 2, + 'Sending DELETE to third tab should change number of tabs' + ); - assertNoElements(t, `#${ex.deletableId}`, `Sending DELETE to third tab should have delete tab with id: ${ex.deletableId}`); -}); + assertNoElements( + t, + `#${ex.deletableId}`, + `Sending DELETE to third tab should have delete tab with id: ${ex.deletableId}` + ); + } +); diff --git a/test/tests/toolbar_toolbar.js b/test/tests/toolbar_toolbar.js index 3064918165..2278f50dff 100644 --- a/test/tests/toolbar_toolbar.js +++ b/test/tests/toolbar_toolbar.js @@ -17,69 +17,92 @@ const ex = { spinSelector: '#ex1 [role="spinbutton"]', checkboxSelector: '#ex1 .item', linkSelector: '#ex1 [href]', - allToolSelectors: [ - '#ex1 .item' - ], + allToolSelectors: ['#ex1 .item'], tabbableItemBeforeToolbarSelector: '[href="../../#toolbar"]', - tabbableItemAfterToolbarSelector: '[href="../../#kbd_roving_tabindex"]' + tabbableItemAfterToolbarSelector: '[href="../../#kbd_roving_tabindex"]', }; const clickAndWait = async function (t, selector) { let element = await t.context.session.findElement(By.css(selector)); await element.click(); - return await t.context.session.wait( - async function () { - let tabindex = await element.getAttribute('tabindex'); - return tabindex === '0'; - }, - t.context.waitTime, - 'Timeout waiting for click to set tabindex="0" on: ' + selector - ).catch((err) => { return err; }); + return await t.context.session + .wait( + async function () { + let tabindex = await element.getAttribute('tabindex'); + return tabindex === '0'; + }, + t.context.waitTime, + 'Timeout waiting for click to set tabindex="0" on: ' + selector + ) + .catch((err) => { + return err; + }); }; const waitAndCheckFocus = async function (t, selector) { - return t.context.session.wait( - async function () { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - let item = document.querySelector(selector); - return item === document.activeElement; - }, selector); - }, - t.context.waitTime, - 'Timeout waiting for activeElement to become: ' + selector, - ).catch((err) => { return err; }); + return t.context.session + .wait( + async function () { + return t.context.session.executeScript(function () { + const [selector, index] = arguments; + let item = document.querySelector(selector); + return item === document.activeElement; + }, selector); + }, + t.context.waitTime, + 'Timeout waiting for activeElement to become: ' + selector + ) + .catch((err) => { + return err; + }); }; const waitAndCheckTabindex = async function (t, selector) { - return t.context.session.wait( - async function () { - let item = await t.context.session.findElement(By.css(selector)); - return (await item.getAttribute('tabindex')) === '0'; - }, - 600, - 'Timeout waiting for tabindex to set to "0" for: ' + selector - ).catch((err) => { return err; }); + return t.context.session + .wait( + async function () { + let item = await t.context.session.findElement(By.css(selector)); + return (await item.getAttribute('tabindex')) === '0'; + }, + 600, + 'Timeout waiting for tabindex to set to "0" for: ' + selector + ) + .catch((err) => { + return err; + }); }; // Attributes -ariaTest('Toolbar element has role="toolbar"', exampleFile, 'toolbar-role', async (t) => { +ariaTest( + 'Toolbar element has role="toolbar"', + exampleFile, + 'toolbar-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'toolbar', '1', 'div'); -}); + } +); // Test fails from bug in example: fix in issue 847 on w3c/aria-practices -ariaTest('Toolbar element has "aria-label"', exampleFile, 'toolbar-aria-label', async (t) => { +ariaTest( + 'Toolbar element has "aria-label"', + exampleFile, + 'toolbar-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.toolbarSelector); -}); - -ariaTest('Toolbar items support roving tabindex on toolbar items (Part 1)', exampleFile, 'toolbar-item-tabindex', async (t) => { - - // Test all the toolbar items with roving tab index - await assertRovingTabindex(t, ex.itemSelector, Key.ARROW_RIGHT); - -}); + } +); + +ariaTest( + 'Toolbar items support roving tabindex on toolbar items (Part 1)', + exampleFile, + 'toolbar-item-tabindex', + async (t) => { + // Test all the toolbar items with roving tab index + await assertRovingTabindex(t, ex.itemSelector, Key.ARROW_RIGHT); + } +); /* ariaTest('Toolbar items support roving tabindex on toolbar items (Part 2)', exampleFile, 'toolbar-item-tabindex', async (t) => { diff --git a/test/tests/treegrid_treegrid-1.js b/test/tests/treegrid_treegrid-1.js index 44c7b908bb..70c3f32cfa 100644 --- a/test/tests/treegrid_treegrid-1.js +++ b/test/tests/treegrid_treegrid-1.js @@ -26,8 +26,8 @@ const ex = { ['2', '3', '3'], ['3', '1', '1'], ['4', '2', '1'], - ['4', '2', '2'] - ] + ['4', '2', '2'], + ], }; const reload = async (t) => { @@ -60,41 +60,53 @@ const openAllThreadsAndFocusOnFirstRow = async function (t) { await openAllThreads(t); await t.context.session.findElement(By.css(ex.gridcellSelector)).click(); - await t.context.session.wait(async function () { - const val = await t.context.session.findElement(By.css(ex.emailRowSelector)) - .getAttribute('tabindex'); - return val === '0'; - }, 500, 'Timeout waiting for tabindex to update to "0" after clicking first grid row'); + await t.context.session.wait( + async function () { + const val = await t.context.session + .findElement(By.css(ex.emailRowSelector)) + .getAttribute('tabindex'); + return val === '0'; + }, + 500, + 'Timeout waiting for tabindex to update to "0" after clicking first grid row' + ); }; const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function (/* selector, index*/) { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function (/* selector, index*/) { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const checkFocusOnParentEmail = async function (t, emailIndex) { - return t.context.session.executeScript(function () { - const [selector, emailIndex] = arguments; + return t.context.session.executeScript( + function () { + const [selector, emailIndex] = arguments; - const emails = document.querySelectorAll(selector); - const email = emails[emailIndex]; - const currentLevel = parseInt(email.getAttribute('aria-level')); + const emails = document.querySelectorAll(selector); + const email = emails[emailIndex]; + const currentLevel = parseInt(email.getAttribute('aria-level')); - const previousLevel = currentLevel - 1; + const previousLevel = currentLevel - 1; - // The first email we find that is up one level is the parent email - for (let i = emailIndex - 1; i >= 0; i--) { - if (parseInt(emails[i].getAttribute('aria-level')) === previousLevel) { - return document.activeElement === emails[i]; + // The first email we find that is up one level is the parent email + for (let i = emailIndex - 1; i >= 0; i--) { + if (parseInt(emails[i].getAttribute('aria-level')) === previousLevel) { + return document.activeElement === emails[i]; + } } - } - return false; - - }, ex.emailRowSelector, emailIndex); + return false; + }, + ex.emailRowSelector, + emailIndex + ); }; const isTopLevelFolder = async function (t, el) { @@ -104,16 +116,17 @@ const isTopLevelFolder = async function (t, el) { }, el); }; -const isOpenedThread = async function (el) { - return await el.getAttribute('aria-expanded') === 'true'; +const isOpenedThread = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'true'; }; -const isClosedThread = async function (el) { - return await el.getAttribute('aria-expanded') === 'false'; +const isClosedThread = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'false'; }; const checkFocusOnGridcell = async function (t, rowIndex, gridcellIndex) { - let gridcellsSelector = '#ex1 [role="row"]:nth-of-type(' + (rowIndex + 1) + ') [role="gridcell"]'; + let gridcellsSelector = + '#ex1 [role="row"]:nth-of-type(' + (rowIndex + 1) + ') [role="gridcell"]'; // If the gridcell is index 0 or 1, it does not contain a widget, focus // should be on the gridcell itself @@ -127,10 +140,16 @@ const checkFocusOnGridcell = async function (t, rowIndex, gridcellIndex) { return checkFocus(t, gridcellsSelector, 0); }; -const sendKeyToGridcellAndWait = async function (t, rowIndex, gridcellIndex, key) { - - let gridcellSelector = '#ex1 [role="row"]:nth-of-type(' + (rowIndex + 1) + ') [role="gridcell"]'; - gridcellSelector = gridcellSelector + ':nth-of-type(' + (gridcellIndex + 1) + ')'; +const sendKeyToGridcellAndWait = async function ( + t, + rowIndex, + gridcellIndex, + key +) { + let gridcellSelector = + '#ex1 [role="row"]:nth-of-type(' + (rowIndex + 1) + ') [role="gridcell"]'; + gridcellSelector = + gridcellSelector + ':nth-of-type(' + (gridcellIndex + 1) + ')'; // If index 2, then we need to send keys to the 'a' element within the gridcell if (gridcellIndex == 2) { @@ -141,7 +160,7 @@ const sendKeyToGridcellAndWait = async function (t, rowIndex, gridcellIndex, key // Wait for focus to move (this example can be slow to update focus on key press) t.context.session.wait(async function () { - return await checkFocus(t, gridcellSelector, 0) === false; + return (await checkFocus(t, gridcellSelector, 0)) === false; }, 500); }; @@ -151,22 +170,24 @@ const sendKeyToRowAndWait = async function (t, rowIndex, key) { // Wait for focus to move (this example can be slow to update focus on key press) t.context.session.wait(async function () { - return await checkFocus(t, ex.emailRowSelector, rowIndex) === false; + return (await checkFocus(t, ex.emailRowSelector, rowIndex)) === false; }, 500); }; const putFocusOnRow1Gridcell = async function (t, columnindex) { if (columnindex === 0) { - await t.context.session.findElement(By.css(ex.emailRowSelector)).sendKeys(Key.ARROW_RIGHT); - } - - else if (columnindex === 1) { - await t.context.session.findElement(By.css(ex.emailRowSelector)).sendKeys(Key.ARROW_RIGHT); + await t.context.session + .findElement(By.css(ex.emailRowSelector)) + .sendKeys(Key.ARROW_RIGHT); + } else if (columnindex === 1) { + await t.context.session + .findElement(By.css(ex.emailRowSelector)) + .sendKeys(Key.ARROW_RIGHT); await sendKeyToGridcellAndWait(t, 0, 0, Key.ARROW_RIGHT); - } - - else if (columnindex === 2) { - await t.context.session.findElement(By.css(ex.emailRowSelector)).sendKeys(Key.ARROW_RIGHT); + } else if (columnindex === 2) { + await t.context.session + .findElement(By.css(ex.emailRowSelector)) + .sendKeys(Key.ARROW_RIGHT); await sendKeyToGridcellAndWait(t, 0, 0, Key.ARROW_RIGHT); await sendKeyToGridcellAndWait(t, 0, 1, Key.ARROW_RIGHT); } @@ -178,400 +199,507 @@ const putFocusOnLastRowGridcell = async function (t, columnindex) { if (columnindex === 0) { await rows[lastrow].sendKeys(Key.ARROW_RIGHT); - } - else if (columnindex === 1) { + } else if (columnindex === 1) { await rows[lastrow].sendKeys(Key.ARROW_RIGHT); await sendKeyToGridcellAndWait(t, lastrow, 0, Key.ARROW_RIGHT); - } - else if (columnindex === 2) { + } else if (columnindex === 2) { await rows[lastrow].sendKeys(Key.ARROW_RIGHT); await sendKeyToGridcellAndWait(t, lastrow, 0, Key.ARROW_RIGHT); await sendKeyToGridcellAndWait(t, lastrow, 1, Key.ARROW_RIGHT); } }; - // Attributes -ariaTest('treegrid role on table element', exampleFile, 'treegrid-role', async (t) => { +ariaTest( + 'treegrid role on table element', + exampleFile, + 'treegrid-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'treegrid', 1, 'table'); -}); + } +); -ariaTest('aria-label on treegrid', exampleFile, 'treegrid-aria-label', async (t) => { +ariaTest( + 'aria-label on treegrid', + exampleFile, + 'treegrid-aria-label', + async (t) => { await assertAriaLabelExists(t, ex.treegridSelector); -}); + } +); ariaTest('row role on tr element', exampleFile, 'row-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'row', 8, 'tr'); + await assertAriaRoles(t, 'ex1', 'row', 8, 'tr'); }); -ariaTest('roving tabindex on rows and links', exampleFile, 'row-tabindex', async (t) => { - - await openAllThreadsAndFocusOnFirstRow(t); - - // Assert roving tab index on rows - await assertRovingTabindex(t, ex.emailRowSelector, Key.ARROW_DOWN); +ariaTest( + 'roving tabindex on rows and links', + exampleFile, + 'row-tabindex', + async (t) => { + await openAllThreadsAndFocusOnFirstRow(t); - await reload(t); - await openAllThreadsAndFocusOnFirstRow(t); + // Assert roving tab index on rows + await assertRovingTabindex(t, ex.emailRowSelector, Key.ARROW_DOWN); - // Assert roving tab index on the links - await assertRovingTabindex(t, ex.emailLinkSelector, Key.ARROW_DOWN); + await reload(t); + await openAllThreadsAndFocusOnFirstRow(t); - // TODO: - // Test tabindex values on gridcells. At the time of this test the - // description does not match the behavior and it is unclear which is correct. - // See issue #790 -}); - -ariaTest('aria-expanded on row elements', exampleFile, 'row-aria-expanded', async (t) => { - - // After page load and after closing the first thread, all threads will be closed - await t.context.session.findElement(By.css(ex.threadSelector)).sendKeys(Key.ARROW_LEFT); - await assertAttributeValues(t, ex.threadSelector, 'aria-expanded', 'false'); - - // Open all threads - await openAllThreadsAndFocusOnFirstRow(t); - await assertAttributeValues(t, ex.threadSelector, 'aria-expanded', 'true'); - - // Close the first thread - await t.context.session.findElement(By.css(ex.threadSelector)).sendKeys(Key.ARROW_LEFT); - - // Make sure the first thread is closed - const threads = await t.context.queryElements(t, ex.threadSelector); - t.is( - await threads.shift().getAttribute('aria-expanded'), - 'false', - 'Closing the first thread (via "arrow left" key) should set aria-expanded to "false"' - ); + // Assert roving tab index on the links + await assertRovingTabindex(t, ex.emailLinkSelector, Key.ARROW_DOWN); - // But all other threads are still open - for (let thread of threads) { + // TODO: + // Test tabindex values on gridcells. At the time of this test the + // description does not match the behavior and it is unclear which is correct. + // See issue #790 + } +); + +ariaTest( + 'aria-expanded on row elements', + exampleFile, + 'row-aria-expanded', + async (t) => { + // After page load and after closing the first thread, all threads will be closed + await t.context.session + .findElement(By.css(ex.threadSelector)) + .sendKeys(Key.ARROW_LEFT); + await assertAttributeValues(t, ex.threadSelector, 'aria-expanded', 'false'); + + // Open all threads + await openAllThreadsAndFocusOnFirstRow(t); + await assertAttributeValues(t, ex.threadSelector, 'aria-expanded', 'true'); + + // Close the first thread + await t.context.session + .findElement(By.css(ex.threadSelector)) + .sendKeys(Key.ARROW_LEFT); + + // Make sure the first thread is closed + const threads = await t.context.queryElements(t, ex.threadSelector); t.is( - await thread.getAttribute('aria-expanded'), - 'true', - 'All threads other than the first thread should remain open after closing on the first.' + await threads.shift().getAttribute('aria-expanded'), + 'false', + 'Closing the first thread (via "arrow left" key) should set aria-expanded to "false"' ); - } -}); + // But all other threads are still open + for (let thread of threads) { + t.is( + await thread.getAttribute('aria-expanded'), + 'true', + 'All threads other than the first thread should remain open after closing on the first.' + ); + } + } +); -ariaTest('aria-level on row elements', exampleFile, 'row-aria-level', async (t) => { - - let rows = await t.context.queryElements(t, ex.emailRowSelector); +ariaTest( + 'aria-level on row elements', + exampleFile, + 'row-aria-level', + async (t) => { + let rows = await t.context.queryElements(t, ex.emailRowSelector); - for (let index = 0; index < rows.length; index++) { - t.is( - await rows[index].getAttribute('aria-level'), - ex.rowRelationData[index][0], - 'The email at index ' + index + ' should have "aria-level" value: ' + ex.rowRelationData[index][0] - ); + for (let index = 0; index < rows.length; index++) { + t.is( + await rows[index].getAttribute('aria-level'), + ex.rowRelationData[index][0], + 'The email at index ' + + index + + ' should have "aria-level" value: ' + + ex.rowRelationData[index][0] + ); + } } -}); +); -ariaTest('aria-setsize on row elements', exampleFile, 'row-aria-setsize', async (t) => { - - let rows = await t.context.queryElements(t, ex.emailRowSelector); +ariaTest( + 'aria-setsize on row elements', + exampleFile, + 'row-aria-setsize', + async (t) => { + let rows = await t.context.queryElements(t, ex.emailRowSelector); - for (let index = 0; index < rows.length; index++) { - t.is( - await rows[index].getAttribute('aria-setsize'), - ex.rowRelationData[index][1], - 'The email at index ' + index + ' should have "aria-setsize" value: ' + ex.rowRelationData[index][1] - ); + for (let index = 0; index < rows.length; index++) { + t.is( + await rows[index].getAttribute('aria-setsize'), + ex.rowRelationData[index][1], + 'The email at index ' + + index + + ' should have "aria-setsize" value: ' + + ex.rowRelationData[index][1] + ); + } } -}); - +); -ariaTest('aria-posinset on row elements', exampleFile, 'row-aria-posinset', async (t) => { - - let rows = await t.context.queryElements(t, ex.emailRowSelector); +ariaTest( + 'aria-posinset on row elements', + exampleFile, + 'row-aria-posinset', + async (t) => { + let rows = await t.context.queryElements(t, ex.emailRowSelector); - for (let index = 0; index < rows.length; index++) { - t.is( - await rows[index].getAttribute('aria-posinset'), - ex.rowRelationData[index][2], - 'The email at index ' + index + ' should have "aria-posinset" value: ' + ex.rowRelationData[index][2] - ); + for (let index = 0; index < rows.length; index++) { + t.is( + await rows[index].getAttribute('aria-posinset'), + ex.rowRelationData[index][2], + 'The email at index ' + + index + + ' should have "aria-posinset" value: ' + + ex.rowRelationData[index][2] + ); + } } -}); - +); -ariaTest('gridcell roles on td elements', exampleFile, 'gridcell-role', async (t) => { +ariaTest( + 'gridcell roles on td elements', + exampleFile, + 'gridcell-role', + async (t) => { await assertAriaRoles(t, 'ex1', 'gridcell', 24, 'td'); -}); + } +); // Keys -ariaTest('Navigating through rows with right arrow', exampleFile, 'key-right-arrow', async (t) => { - - await closeAllThreads(t); - - // Going through all closed email thread elements in dom order will open parent - // threads first. +ariaTest( + 'Navigating through rows with right arrow', + exampleFile, + 'key-right-arrow', + async (t) => { + await closeAllThreads(t); + + // Going through all closed email thread elements in dom order will open parent + // threads first. + + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); + for (let index = 0; index < emailRows.length; index++) { + // Send ARROW RIGHT only to emails that are the start of threads + if (await isClosedThread(emailRows[index])) { + await emailRows[index].sendKeys(Key.ARROW_RIGHT); + t.true( + await emailRows[index + 1].isDisplayed(), + 'Sending key ARROW RIGHT to email at index ' + + index + + ' should open a thread, displaying the next email row(s) in the gridtree.' + ); + } + } + } +); + +ariaTest( + 'Navigating through gridcells with right arrow', + exampleFile, + 'key-right-arrow', + async (t) => { + await openAllThreads(t); + + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); + for (let index = 0; index < emailRows.length; index++) { + // Check that arrow right moves focus to the first gridcell of each row if the email + // is not the start of a thread, or the thread is open + await emailRows[index].sendKeys(Key.ARROW_RIGHT); + t.true( + await checkFocusOnGridcell(t, index, 0), + 'Sending key ARROW RIGHT to email at index ' + + index + + ' should move focus to the first gridcell' + ); - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - for (let index = 0; index < emailRows.length; index++) { + // Check that arrow right moves focus to the next gridcell + await sendKeyToGridcellAndWait(t, index, 0, Key.ARROW_RIGHT); + t.true( + await checkFocusOnGridcell(t, index, 1), + 'Sending ARROW RIGHT keys to gridcell at index 1 should move focus to gridcell at index 2' + ); - // Send ARROW RIGHT only to emails that are the start of threads - if (await isClosedThread(emailRows[index])) { - await emailRows[index].sendKeys(Key.ARROW_RIGHT); + // Check that arrow right does not move focus when sent to last gridcell + await sendKeyToGridcellAndWait(t, index, 2, Key.ARROW_RIGHT); t.true( - await emailRows[index + 1].isDisplayed(), - 'Sending key ARROW RIGHT to email at index ' + index + ' should open a thread, displaying the next email row(s) in the gridtree.' + await checkFocusOnGridcell(t, index, 2), + 'Sending ARROW RIGHT keys to final gridcell should key focus on final gridcell' ); } } +); + +ariaTest( + 'Navigating through rows with left arrow', + exampleFile, + 'key-left-arrow', + async (t) => { + await openAllThreads(t); + + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); + let i = emailRows.length - 1; + while (i > 0) { + const isOpened = await isOpenedThread(emailRows[i]); + + await emailRows[i].sendKeys(Key.ARROW_LEFT); + + // If the emailRows was an opened Thread + if (isOpened) { + t.is( + await emailRows[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_LEFT to thread at email index ' + + i + + ' when the thread is opened should close the thread' + ); + + t.true( + await checkFocus(t, ex.emailRowSelector, i), + 'Sending key ARROW_LEFT to thread at email index ' + + i + + ' when the thread is opened should not move the focus' + ); + // Send one more arrow key to the folder that is now closed + continue; + } -}); - -ariaTest('Navigating through gridcells with right arrow', exampleFile, 'key-right-arrow', async (t) => { - - await openAllThreads(t); - - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - for (let index = 0; index < emailRows.length; index++) { - - // Check that arrow right moves focus to the first gridcell of each row if the email - // is not the start of a thread, or the thread is open - await emailRows[index].sendKeys(Key.ARROW_RIGHT); - t.true( - await checkFocusOnGridcell(t, index, 0), - 'Sending key ARROW RIGHT to email at index ' + index + ' should move focus to the first gridcell' - ); - - // Check that arrow right moves focus to the next gridcell - await sendKeyToGridcellAndWait(t, index, 0, Key.ARROW_RIGHT); - t.true( - await checkFocusOnGridcell(t, index, 1), - 'Sending ARROW RIGHT keys to gridcell at index 1 should move focus to gridcell at index 2' - ); + // If the email row is an email without children, or a closed thead, + // arrow will move up to parent email + else { + t.true( + await checkFocusOnParentEmail(t, i), + 'Sending key ARROW_LEFT to email at index ' + + i + + ' should move focus to parent email' + ); + + t.is( + await emailRows[i].isDisplayed(), + true, + 'Sending key ARROW_LEFT to email at index ' + + i + + ' should not close the folder it is in' + ); + } - // Check that arrow right does not move focus when sent to last gridcell - await sendKeyToGridcellAndWait(t, index, 2, Key.ARROW_RIGHT); - t.true( - await checkFocusOnGridcell(t, index, 2), - 'Sending ARROW RIGHT keys to final gridcell should key focus on final gridcell' - ); + i--; + } } +); -}); +ariaTest( + 'Navigating through gridcells with left arrow', + exampleFile, + 'key-left-arrow', + async (t) => { + await openAllThreads(t); -ariaTest('Navigating through rows with left arrow', exampleFile, 'key-left-arrow', async (t) => { - - await openAllThreads(t); + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - let i = emailRows.length - 1; - while (i > 0) { + for (let index = 0; index < emailRows.length; index++) { + // Set up: put focus on a gridcell by sending a "ARROW LEFT" key to an open row + await emailRows[index].sendKeys(Key.ARROW_RIGHT); - const isOpened = await isOpenedThread(emailRows[i]); + // Now send that row an "ARROW LEFT" key and make sure focus is on the first row + await sendKeyToGridcellAndWait(t, index, 0, Key.ARROW_LEFT); + t.true( + await checkFocus(t, ex.emailRowSelector, index), + 'Focus should be on grid row at index " + index + " after ARROW LEFT sent to first gridcell in first row' + ); - await emailRows[i].sendKeys(Key.ARROW_LEFT); + // Set up: put focus on last gridcell + await emailRows[index].sendKeys(Key.ARROW_RIGHT); + await sendKeyToGridcellAndWait(t, index, 0, Key.END); - // If the emailRows was an opened Thread - if (isOpened) { - t.is( - await emailRows[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_LEFT to thread at email index ' + i + - ' when the thread is opened should close the thread' + // Now send that gridcell an "ARROW LEFT" key and make sure focus is on the second gridcell + await sendKeyToGridcellAndWait(t, index, 2, Key.ARROW_LEFT); + t.true( + await checkFocusOnGridcell(t, index, 1), + 'Focus should be on the second gridcell in the first row after ARROW LEFT sent to third gridcell in grid row of index: ' + + index ); + // Now send the second gridcell an "ARROW LEFT" key and make sure focus is on the first + await sendKeyToGridcellAndWait(t, index, 1, Key.ARROW_LEFT); t.true( - await checkFocus(t, ex.emailRowSelector, i), - 'Sending key ARROW_LEFT to thread at email index ' + i + - ' when the thread is opened should not move the focus' + await checkFocusOnGridcell(t, index, 0), + 'Focus should be on the first gridcell in the first row after ARROW LEFT sent to second gridcell in grid row of index: ' + + index ); - // Send one more arrow key to the folder that is now closed - continue; } - - // If the email row is an email without children, or a closed thead, - // arrow will move up to parent email - else { + } +); + +ariaTest( + 'Navigating through rows with down arrow', + exampleFile, + 'key-down-arrow', + async (t) => { + await openAllThreads(t); + + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); + for (let index = 0; index < emailRows.length - 1; index++) { + // Send ARROW DOWN to email rows + await emailRows[index].sendKeys(Key.ARROW_DOWN); t.true( - await checkFocusOnParentEmail(t, i), - 'Sending key ARROW_LEFT to email at index ' + i + ' should move focus to parent email' - ); - - t.is( - await emailRows[i].isDisplayed(), - true, - 'Sending key ARROW_LEFT to email at index ' + i + ' should not close the folder it is in' + await checkFocus(t, ex.emailRowSelector, index + 1), + 'Sending key ARROW DOWN to email at index ' + + index + + ' should move focus to the next email' ); } - i--; - } -}); - - -ariaTest('Navigating through gridcells with left arrow', exampleFile, 'key-left-arrow', async (t) => { - - await openAllThreads(t); - - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - - for (let index = 0; index < emailRows.length; index++) { - - // Set up: put focus on a gridcell by sending a "ARROW LEFT" key to an open row - await emailRows[index].sendKeys(Key.ARROW_RIGHT); - - // Now send that row an "ARROW LEFT" key and make sure focus is on the first row - await sendKeyToGridcellAndWait(t, index, 0, Key.ARROW_LEFT); + // Send ARROW DOWN to last email + await emailRows[emailRows.length - 1].sendKeys(Key.ARROW_DOWN); t.true( - await checkFocus(t, ex.emailRowSelector, index), - 'Focus should be on grid row at index " + index + " after ARROW LEFT sent to first gridcell in first row' - ); - - // Set up: put focus on last gridcell - await emailRows[index].sendKeys(Key.ARROW_RIGHT); - await sendKeyToGridcellAndWait(t, index, 0, Key.END); - - // Now send that gridcell an "ARROW LEFT" key and make sure focus is on the second gridcell - await sendKeyToGridcellAndWait(t, index, 2, Key.ARROW_LEFT); - t.true( - await checkFocusOnGridcell(t, index, 1), - 'Focus should be on the second gridcell in the first row after ARROW LEFT sent to third gridcell in grid row of index: ' + index - ); - - // Now send the second gridcell an "ARROW LEFT" key and make sure focus is on the first - await sendKeyToGridcellAndWait(t, index, 1, Key.ARROW_LEFT); - t.true( - await checkFocusOnGridcell(t, index, 0), - 'Focus should be on the first gridcell in the first row after ARROW LEFT sent to second gridcell in grid row of index: ' + index + await checkFocus(t, ex.emailRowSelector, emailRows.length - 1), + 'Sending key ARROW DOWN to last email should not move focus' ); } +); + +ariaTest( + 'Navigating through gridcells with down arrow', + exampleFile, + 'key-down-arrow', + async (t) => { + await openAllThreads(t); + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); + + for (let columnindex = 0; columnindex < 3; columnindex++) { + await putFocusOnRow1Gridcell(t, columnindex); + + for (let rowindex = 0; rowindex < emailRows.length - 1; rowindex++) { + // Send ARROW DOWN to gridcell + await sendKeyToGridcellAndWait( + t, + rowindex, + columnindex, + Key.ARROW_DOWN + ); + t.true( + await checkFocusOnGridcell(t, rowindex + 1, columnindex), + 'Sending key ARROW DOWN to cell at row index ' + + rowindex + + ' and column index ' + + columnindex + + ' should move focus to the next email' + ); + } -}); - -ariaTest('Navigating through rows with down arrow', exampleFile, 'key-down-arrow', async (t) => { - - await openAllThreads(t); + // Send ARROW DOWN to last row + await sendKeyToGridcellAndWait( + t, + emailRows.length - 1, + columnindex, + Key.ARROW_DOWN + ); + t.true( + await checkFocusOnGridcell(t, emailRows.length - 1, columnindex), + 'Sending key ARROW DOWN to the cell at column index ' + + columnindex + + ' in the last row should not move focus' + ); + } + } +); + +ariaTest( + 'Navigating through rows with up arrow', + exampleFile, + 'key-up-arrow', + async (t) => { + await openAllThreads(t); + + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); + for (let index = emailRows.length - 1; index > 0; index--) { + // Send ARROW UP to email rows + await emailRows[index].sendKeys(Key.ARROW_UP); + t.true( + await checkFocus(t, ex.emailRowSelector, index - 1), + 'Sending key ARROW UP to email at index ' + + index + + ' should move focus to the previous email' + ); + } - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - for (let index = 0; index < emailRows.length - 1; index++) { - // Send ARROW DOWN to email rows - await emailRows[index].sendKeys(Key.ARROW_DOWN); + // Send ARROW UP to first email + await emailRows[0].sendKeys(Key.ARROW_UP); t.true( - await checkFocus(t, ex.emailRowSelector, index + 1), - 'Sending key ARROW DOWN to email at index ' + index + ' should move focus to the next email' + await checkFocus(t, ex.emailRowSelector, 0), + 'Sending key ARROW UP to first email should not move focus' ); } +); + +ariaTest( + 'Navigating through gridcells with up arrow', + exampleFile, + 'key-up-arrow', + async (t) => { + await openAllThreads(t); + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); + + for (let columnindex = 0; columnindex < 3; columnindex++) { + await putFocusOnLastRowGridcell(t, columnindex); + + for (let rowindex = emailRows.length - 1; rowindex > 0; rowindex--) { + // Send ARROW UP to gridcell + await sendKeyToGridcellAndWait(t, rowindex, columnindex, Key.ARROW_UP); + t.true( + await checkFocusOnGridcell(t, rowindex - 1, columnindex), + 'Sending key ARROW UP to cell at row index ' + + rowindex + + ' and column index ' + + columnindex + + ' should move focus to the previous email' + ); + } - // Send ARROW DOWN to last email - await emailRows[emailRows.length - 1].sendKeys(Key.ARROW_DOWN); - t.true( - await checkFocus(t, ex.emailRowSelector, emailRows.length - 1), - 'Sending key ARROW DOWN to last email should not move focus' - ); - -}); - -ariaTest('Navigating through gridcells with down arrow', exampleFile, 'key-down-arrow', async (t) => { - - await openAllThreads(t); - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - - for (let columnindex = 0; columnindex < 3; columnindex++) { - await putFocusOnRow1Gridcell(t, columnindex); - - for (let rowindex = 0; rowindex < emailRows.length - 1; rowindex++) { - // Send ARROW DOWN to gridcell - await sendKeyToGridcellAndWait(t, rowindex, columnindex, Key.ARROW_DOWN); + // Send ARROW UP to first row + await sendKeyToGridcellAndWait(t, 0, columnindex, Key.ARROW_UP); t.true( - await checkFocusOnGridcell(t, rowindex + 1, columnindex), - 'Sending key ARROW DOWN to cell at row index ' + rowindex + ' and column index ' + columnindex + ' should move focus to the next email' + await checkFocusOnGridcell(t, 0, columnindex), + 'Sending key ARROW UUP to the cell at column index ' + + columnindex + + ' in the first row should not move focus' ); } - - // Send ARROW DOWN to last row - await sendKeyToGridcellAndWait(t, emailRows.length - 1, columnindex, Key.ARROW_DOWN); - t.true( - await checkFocusOnGridcell(t, emailRows.length - 1, columnindex), - 'Sending key ARROW DOWN to the cell at column index ' + columnindex + ' in the last row should not move focus' - ); } -}); +); -ariaTest('Navigating through rows with up arrow', exampleFile, 'key-up-arrow', async (t) => { - - await openAllThreads(t); +ariaTest( + 'TAB moves focus from active row to widgets in row', + exampleFile, + 'key-tab', + async (t) => { + // Send tab to the first row: + await sendKeyToRowAndWait(t, 0, Key.TAB); - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - for (let index = emailRows.length - 1; index > 0; index--) { - // Send ARROW UP to email rows - await emailRows[index].sendKeys(Key.ARROW_UP); t.true( - await checkFocus(t, ex.emailRowSelector, index - 1), - 'Sending key ARROW UP to email at index ' + index + ' should move focus to the previous email' + await checkFocusOnGridcell(t, 0, 2), + 'Sending TAB to the first row on page load will send the focus to the first interactive widget in the row, which should be the email link in the last column.' ); } +); - // Send ARROW UP to first email - await emailRows[0].sendKeys(Key.ARROW_UP); - t.true( - await checkFocus(t, ex.emailRowSelector, 0), - 'Sending key ARROW UP to first email should not move focus' - ); -}); - -ariaTest('Navigating through gridcells with up arrow', exampleFile, 'key-up-arrow', async (t) => { - - await openAllThreads(t); - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - - for (let columnindex = 0; columnindex < 3; columnindex++) { - await putFocusOnLastRowGridcell(t, columnindex); +ariaTest( + 'SHIFT+TAB moves focus from widgets in row to row', + exampleFile, + 'key-shift-tab', + async (t) => { + await putFocusOnRow1Gridcell(t, 2); - for (let rowindex = emailRows.length - 1; rowindex > 0 ; rowindex--) { - // Send ARROW UP to gridcell - await sendKeyToGridcellAndWait(t, rowindex, columnindex, Key.ARROW_UP); - t.true( - await checkFocusOnGridcell(t, rowindex - 1, columnindex), - 'Sending key ARROW UP to cell at row index ' + rowindex + ' and column index ' + columnindex + ' should move focus to the previous email' - ); - } + // Send shift tab to the link in the last gridcell of the first row + await sendKeyToGridcellAndWait(t, 0, 2, Key.chord(Key.SHIFT, Key.TAB)); - // Send ARROW UP to first row - await sendKeyToGridcellAndWait(t, 0, columnindex, Key.ARROW_UP); t.true( - await checkFocusOnGridcell(t, 0, columnindex), - 'Sending key ARROW UUP to the cell at column index ' + columnindex + ' in the first row should not move focus' + await checkFocus(t, ex.emailRowSelector, 0), + 'Sending SHIFT+TAB to the interactive widget in the last cell of the first row should move focus to the first row.' ); } - -}); - - -ariaTest('TAB moves focus from active row to widgets in row', exampleFile, 'key-tab', async (t) => { - - // Send tab to the first row: - await sendKeyToRowAndWait(t, 0, Key.TAB); - - t.true( - await checkFocusOnGridcell(t, 0, 2), - 'Sending TAB to the first row on page load will send the focus to the first interactive widget in the row, which should be the email link in the last column.' - ); -}); - -ariaTest('SHIFT+TAB moves focus from widgets in row to row', exampleFile, 'key-shift-tab', async (t) => { - - await putFocusOnRow1Gridcell(t, 2); - - // Send shift tab to the link in the last gridcell of the first row - await sendKeyToGridcellAndWait(t, 0, 2, Key.chord(Key.SHIFT, Key.TAB)); - - t.true( - await checkFocus(t, ex.emailRowSelector, 0), - 'Sending SHIFT+TAB to the interactive widget in the last cell of the first row should move focus to the first row.' - ); - -}); +); ariaTest('HOME moves focus', exampleFile, 'key-home', async (t) => { - await openAllThreads(t); const emailRows = await t.context.queryElements(t, ex.emailRowSelector); @@ -587,12 +715,11 @@ ariaTest('HOME moves focus', exampleFile, 'key-home', async (t) => { await sendKeyToGridcellAndWait(t, 0, 1, Key.HOME); t.true( await checkFocusOnGridcell(t, 0, 0), - 'Sending HOME to first row\'s second gridcell should move focus to first row\'s first gridcell' + "Sending HOME to first row's second gridcell should move focus to first row's first gridcell" ); }); ariaTest('END moves focus', exampleFile, 'key-end', async (t) => { - await openAllThreads(t); const emailRows = await t.context.queryElements(t, ex.emailRowSelector); @@ -609,33 +736,41 @@ ariaTest('END moves focus', exampleFile, 'key-end', async (t) => { await sendKeyToGridcellAndWait(t, 0, 0, Key.END); t.true( await checkFocusOnGridcell(t, 0, 2), - 'Sending HOME to first row\'s first gridcell should move focus to first row\'s last gridcell' + "Sending HOME to first row's first gridcell should move focus to first row's last gridcell" ); }); -ariaTest('CTRL+HOME moves focus', exampleFile, 'key-control-home', async (t) => { - - await openAllThreads(t); - const emailRows = await t.context.queryElements(t, ex.emailRowSelector); +ariaTest( + 'CTRL+HOME moves focus', + exampleFile, + 'key-control-home', + async (t) => { + await openAllThreads(t); + const emailRows = await t.context.queryElements(t, ex.emailRowSelector); - // Send CTRL+HOME to the second row - await emailRows[1].sendKeys(Key.chord(Key.CONTROL, Key.HOME)); - t.true( - await checkFocus(t, ex.emailRowSelector, 0), - 'Sending CTRL+HOME to second row should result on focus on first row' - ); + // Send CTRL+HOME to the second row + await emailRows[1].sendKeys(Key.chord(Key.CONTROL, Key.HOME)); + t.true( + await checkFocus(t, ex.emailRowSelector, 0), + 'Sending CTRL+HOME to second row should result on focus on first row' + ); - // Send CTRL+HOME to last row first gridcell - await putFocusOnLastRowGridcell(t, 0); - await sendKeyToGridcellAndWait(t, ex.lastRowIndex, 0, Key.chord(Key.CONTROL, Key.HOME)); - t.true( - await checkFocusOnGridcell(t, 0, 0), - 'Sending CTRL+HOME to last row\'s first gridcell should move focus to first row\'s first gridcell' - ); -}); + // Send CTRL+HOME to last row first gridcell + await putFocusOnLastRowGridcell(t, 0); + await sendKeyToGridcellAndWait( + t, + ex.lastRowIndex, + 0, + Key.chord(Key.CONTROL, Key.HOME) + ); + t.true( + await checkFocusOnGridcell(t, 0, 0), + "Sending CTRL+HOME to last row's first gridcell should move focus to first row's first gridcell" + ); + } +); ariaTest('CTRL+END moves focus', exampleFile, 'key-control-end', async (t) => { - await openAllThreads(t); const emailRows = await t.context.queryElements(t, ex.emailRowSelector); @@ -651,44 +786,56 @@ ariaTest('CTRL+END moves focus', exampleFile, 'key-control-end', async (t) => { await sendKeyToGridcellAndWait(t, 0, 0, Key.chord(Key.CONTROL, Key.END)); t.true( await checkFocusOnGridcell(t, ex.lastRowIndex, 0), - 'Sending CTRL+END to first row\'s first gridcell should move focus to last row\'s first gridcell' + "Sending CTRL+END to first row's first gridcell should move focus to last row's first gridcell" ); }); // This test fails due to: https://github.com/w3c/aria-practices/issues/790#issuecomment-422079276 -ariaTest.failing('ENTER actives interactive items item', exampleFile, 'key-enter', async (t) => { - - // INTERACTIVE ITEM 1: Enter sent while focus is on email row should open email alert - - const email = await t.context.session.findElement(By.css(ex.emailRowSelector)); - - await email.sendKeys(Key.ENTER); +ariaTest.failing( + 'ENTER actives interactive items item', + exampleFile, + 'key-enter', + async (t) => { + // INTERACTIVE ITEM 1: Enter sent while focus is on email row should open email alert + + const email = await t.context.session.findElement( + By.css(ex.emailRowSelector) + ); - const alert = await t.context.session.wait(until.alertIsPresent(), t.context.waitTime); - t.truthy( - await alert.getText(), - 'Sending "enter" to any email row should open alert with the rest of the email' - ); - await alert.accept(); + await email.sendKeys(Key.ENTER); - // INTERACTIVE ITEM 1: Enter sent while focus is email gridcell should trigger link + const alert = await t.context.session.wait( + until.alertIsPresent(), + t.context.waitTime + ); + t.truthy( + await alert.getText(), + 'Sending "enter" to any email row should open alert with the rest of the email' + ); + await alert.accept(); - const selector = '#ex1 [role="row"]:nth-of-type(1) a'; - const newUrl = t.context.url + '#test-url-change'; + // INTERACTIVE ITEM 1: Enter sent while focus is email gridcell should trigger link - // Reset the href to not be an email link in order to test - await t.context.session.executeScript(function () { - let [selector, newUrl] = arguments; - document.querySelector(selector).href = newUrl; - }, selector, newUrl); + const selector = '#ex1 [role="row"]:nth-of-type(1) a'; + const newUrl = t.context.url + '#test-url-change'; - await t.context.session.findElement(By.css(selector)).sendKeys(Key.ENTER); + // Reset the href to not be an email link in order to test + await t.context.session.executeScript( + function () { + let [selector, newUrl] = arguments; + document.querySelector(selector).href = newUrl; + }, + selector, + newUrl + ); - // Test that the URL is updated. - t.is( - await t.context.session.getCurrentUrl(), - newUrl, - 'Sending "enter" to a link within the gridcell should activate the link' - ); -}); + await t.context.session.findElement(By.css(selector)).sendKeys(Key.ENTER); + // Test that the URL is updated. + t.is( + await t.context.session.getCurrentUrl(), + newUrl, + 'Sending "enter" to a link within the gridcell should activate the link' + ); + } +); diff --git a/test/tests/treeview_treeview-1a.js b/test/tests/treeview_treeview-1a.js index e2fec4e87a..9a125df253 100644 --- a/test/tests/treeview_treeview-1a.js +++ b/test/tests/treeview_treeview-1a.js @@ -16,7 +16,7 @@ const ex = { topLevelFolderSelector: '#ex1 [role="tree"] > [role="treeitem"]', nextLevelFolderSelector: '[role="group"] > [role="treeitem"][aria-expanded]', docSelector: '#ex1 .doc[role="treeitem"]', - textboxSelector: '#ex1 #last_action' + textboxSelector: '#ex1 #last_action', }; const openAllFolders = async function (t) { @@ -31,11 +31,15 @@ const openAllFolders = async function (t) { }; const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function (/* selector, index*/) { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function (/* selector, index*/) { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const checkFocusOnParentFolder = async function (t, el) { @@ -44,11 +48,17 @@ const checkFocusOnParentFolder = async function (t, el) { // the element is a folder if (el.hasAttribute('aria-expanded')) { - return document.activeElement === el.parentElement.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.parentElement.closest('[role="treeitem"][aria-expanded]') + ); } // the element is a folder else { - return document.activeElement === el.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.closest('[role="treeitem"][aria-expanded]') + ); } }, el); }; @@ -64,17 +74,15 @@ const isFolderTreeitem = async function (el) { return !(await el.getAttribute('class')).includes('doc'); }; -const isOpenedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'true'; +const isOpenedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'true'; }; -const isClosedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'false'; +const isClosedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'false'; }; ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { - - const trees = await t.context.queryElements(t, ex.treeSelector); t.is( @@ -90,237 +98,278 @@ ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { ); }); -ariaTest('aria-labelledby on role="tree" element', exampleFile, 'tree-aria-labelledby', async (t) => { - - - await assertAriaLabelledby(t, ex.treeSelector); -}); - -ariaTest('role="treeitem" on "ul" element', exampleFile, 'treeitem-role', async (t) => { - - - const treeitems = await t.context.queryElements(t, ex.treeitemSelector); - - t.truthy( - treeitems.length, - 'role="treeitem" elements should be found by selector: ' + ex.treeitemSelector - ); - - for (let treeitem of treeitems) { - t.is( - await treeitem.getTagName(), - 'li', - 'role="treeitem" should be found on a "li"' +ariaTest( + 'aria-labelledby on role="tree" element', + exampleFile, + 'tree-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.treeSelector); + } +); + +ariaTest( + 'role="treeitem" on "ul" element', + exampleFile, + 'treeitem-role', + async (t) => { + const treeitems = await t.context.queryElements(t, ex.treeitemSelector); + + t.truthy( + treeitems.length, + 'role="treeitem" elements should be found by selector: ' + + ex.treeitemSelector ); + + for (let treeitem of treeitems) { + t.is( + await treeitem.getTagName(), + 'li', + 'role="treeitem" should be found on a "li"' + ); + } } -}); +); -ariaTest('treeitem tabindex set by roving tabindex', exampleFile, 'treeitem-tabindex', async (t) => { +ariaTest( + 'treeitem tabindex set by roving tabindex', + exampleFile, + 'treeitem-tabindex', + async (t) => { await openAllFolders(t); - await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); -}); + await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); + } +); + +ariaTest( + 'aria-expanded attribute on treeitem matches dom', + exampleFile, + 'treeitem-ariaexpanded', + async (t) => { + const folders = await t.context.queryElements(t, ex.folderSelector); + + for (let folder of folders) { + // If the folder is displayed + if (await folder.isDisplayed()) { + const folderText = await folder.getText(); + + // By default, all folders will be closed + t.is(await folder.getAttribute('aria-expanded'), 'false'); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + false + ); + + // Send enter to the folder + await folder.sendKeys(Key.ENTER); + + // After click, it should be open + t.is(await folder.getAttribute('aria-expanded'), 'true'); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + true + ); + } + } -ariaTest('aria-expanded attribute on treeitem matches dom', exampleFile, 'treeitem-ariaexpanded', async (t) => { + for (let i = folders.length - 1; i >= 0; i--) { + // If the folder is displayed + if (await folders[i].isDisplayed()) { + const folderText = await folders[i].getText(); + + // Send enter to the folder + await folders[i].sendKeys(Key.ENTER); + + // After sending enter, it should be closed + t.is( + await folders[i].getAttribute('aria-expanded'), + 'false', + folderText + ); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folders[i]) + ).isDisplayed(), + false, + folderText + ); + } + } + } +); + +ariaTest( + 'role="group" on "ul" elements', + exampleFile, + 'group-role', + async (t) => { + const groups = await t.context.queryElements(t, ex.groupSelector); + + t.truthy( + groups.length, + 'role="group" elements should be found by selector: ' + ex.groupSelector + ); - - const folders = await t.context.queryElements(t, ex.folderSelector); + for (let group of groups) { + t.is( + await group.getTagName(), + 'ul', + 'role="group" should be found on a "ul"' + ); + } + } +); - for (let folder of folders) { +// Keys - // If the folder is displayed - if (await folder.isDisplayed()) { +ariaTest( + 'Key enter opens folder', + exampleFile, + 'key-enter-or-space', + async (t) => { + await openAllFolders(t); - const folderText = await folder.getText(); + const items = await t.context.queryElements(t, ex.docSelector); - // By default, all folders will be closed - t.is( - await folder.getAttribute('aria-expanded'), - 'false' - ); - t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - false - ); + for (let item of items) { + const itemText = await item.getText(); // Send enter to the folder - await folder.sendKeys(Key.ENTER); + await item.sendKeys(Key.ENTER); - // After click, it should be open - t.is( - await folder.getAttribute('aria-expanded'), - 'true' - ); + const boxText = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('value'); t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - true + boxText, + itemText, + 'Sending enter to doc "' + + itemText + + '" should update the value in the textbox' ); } } +); - for (let i = (folders.length - 1); i >= 0; i--) { +ariaTest( + 'Key space opens folder', + exampleFile, + 'key-enter-or-space', + async (t) => { + await openAllFolders(t); - // If the folder is displayed - if (await folders[i].isDisplayed()) { + const items = await t.context.queryElements(t, ex.docSelector); - const folderText = await folders[i].getText(); + for (let item of items) { + const itemText = await item.getText(); // Send enter to the folder - await folders[i].sendKeys(Key.ENTER); + await item.sendKeys(Key.SPACE); - // After sending enter, it should be closed - t.is( - await folders[i].getAttribute('aria-expanded'), - 'false', - folderText - ); + const boxText = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('value'); t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folders[i])).isDisplayed(), - false, - folderText + boxText, + itemText, + 'Sending space to doc "' + + itemText + + '" should update the value in the textbox' ); } } - -}); - -ariaTest('role="group" on "ul" elements', exampleFile, 'group-role', async (t) => { - - - const groups = await t.context.queryElements(t, ex.groupSelector); - - t.truthy( - groups.length, - 'role="group" elements should be found by selector: ' + ex.groupSelector - ); - - for (let group of groups) { - t.is( - await group.getTagName(), - 'ul', - 'role="group" should be found on a "ul"' - ); - } -}); - -// Keys - -ariaTest('Key enter opens folder', exampleFile, 'key-enter-or-space', async (t) => { - - await openAllFolders(t); - - const items = await t.context.queryElements(t, ex.docSelector); - - for (let item of items) { - - const itemText = await item.getText(); - - // Send enter to the folder - await item.sendKeys(Key.ENTER); - - const boxText = await t.context.session.findElement(By.css(ex.textboxSelector)).getAttribute('value'); - t.is( - boxText, - itemText, - 'Sending enter to doc "' + itemText + '" should update the value in the textbox' - ); - } - -}); - -ariaTest('Key space opens folder', exampleFile, 'key-enter-or-space', async (t) => { - - await openAllFolders(t); - - const items = await t.context.queryElements(t, ex.docSelector); - - for (let item of items) { - - const itemText = await item.getText(); - - // Send enter to the folder - await item.sendKeys(Key.SPACE); - - const boxText = await t.context.session.findElement(By.css(ex.textboxSelector)).getAttribute('value'); - t.is( - boxText, - itemText, - 'Sending space to doc "' + itemText + '" should update the value in the textbox' +); + +ariaTest( + 'key down arrow moves focus', + exampleFile, + 'key-down-arrow', + async (t) => { + // Check that the down arrow does not open folders + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector ); - } - -}); -ariaTest('key down arrow moves focus', exampleFile, 'key-down-arrow', async (t) => { - - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - - for (let i = 0; i < topLevelFolders.length; i++) { - await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); + for (let i = 0; i < topLevelFolders.length; i++) { + await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); - // If we are on the last top level folder, the focus will not move - const nextIndex = i === topLevelFolders.length - 1 ? - i : - i + 1; + // If we are on the last top level folder, the focus will not move + const nextIndex = i === topLevelFolders.length - 1 ? i : i + 1; - t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex + ); - t.is( - await topLevelFolders[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' should not expand the folder' - ); - } + t.is( + await topLevelFolders[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' should not expand the folder' + ); + } - // Reload page - await t.context.session.get(t.context.url); + // Reload page + await t.context.session.get(t.context.url); - // Open all folders - await openAllFolders(t); + // Open all folders + await openAllFolders(t); - const items = await t.context.queryElements(t, ex.treeitemSelector); + const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = 0; i < items.length; i++) { - await items[i].sendKeys(Key.ARROW_DOWN); + for (let i = 0; i < items.length; i++) { + await items[i].sendKeys(Key.ARROW_DOWN); - // If we are on the last item, the focus will not move - const nextIndex = i === items.length - 1 ? - i : - i + 1; + // If we are on the last item, the focus will not move + const nextIndex = i === items.length - 1 ? i : i + 1; - t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_DOWN to folder/item at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_DOWN to folder/item at index ' + + i + + ' will move focus to ' + + nextIndex + ); + } } -}); +); ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.ARROW_UP); // If we are on the last top level folder, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_UP to top level folder at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key ARROW_UP to top level folder at index ' + i + ' should not expand the folder' + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' should not expand the folder' ); } @@ -332,157 +381,177 @@ ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.ARROW_UP); // If we are on the last item, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_UP to folder/item at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_UP to folder/item at index ' + + i + + ' will move focus to ' + + nextIndex ); } }); -ariaTest('key right arrow opens folders and moves focus', exampleFile, 'key-right-arrow', async (t) => { - - const items = await t.context.queryElements(t, ex.treeitemSelector); - - let i = 0; - while (i < items.length) { - - const isFolder = await isFolderTreeitem(items[i]); - const isClosed = await isClosedFolderTreeitem(items[i]); - - await items[i].sendKeys(Key.ARROW_RIGHT); - - // If the item is a folder and it was originally closed - if (isFolder && isClosed) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'true', - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder is closed should open the folder' - ); - - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder was closed should not move the focus' - ); - continue; - } - - // If the folder is an open folder, the focus will move - else if (isFolder) { - t.true( - await checkFocus(t, ex.treeitemSelector, i + 1), - 'Sending key ARROW_RIGHT to open folder at treeitem index ' + i + - ' should move focus to item ' + (i + 1) - ); - } - - // If we are a document, the focus will not move - else { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to document item at treeitem index ' + i + ' should not move focus' - ); - +ariaTest( + 'key right arrow opens folders and moves focus', + exampleFile, + 'key-right-arrow', + async (t) => { + const items = await t.context.queryElements(t, ex.treeitemSelector); + + let i = 0; + while (i < items.length) { + const isFolder = await isFolderTreeitem(items[i]); + const isClosed = await isClosedFolderTreeitem(items[i]); + + await items[i].sendKeys(Key.ARROW_RIGHT); + + // If the item is a folder and it was originally closed + if (isFolder && isClosed) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'true', + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder is closed should open the folder' + ); + + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder was closed should not move the focus' + ); + continue; + } + + // If the folder is an open folder, the focus will move + else if (isFolder) { + t.true( + await checkFocus(t, ex.treeitemSelector, i + 1), + 'Sending key ARROW_RIGHT to open folder at treeitem index ' + + i + + ' should move focus to item ' + + (i + 1) + ); + } + + // If we are a document, the focus will not move + else { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to document item at treeitem index ' + + i + + ' should not move focus' + ); + } + i++; } - i++; } -}); - -ariaTest('key left arrow closes folders and moves focus', exampleFile, 'key-left-arrow', async (t) => { - - // Open all folders - await openAllFolders(t); - - const items = await t.context.queryElements(t, ex.treeitemSelector); - - let i = items.length - 1; - while (i > 0) { - - const isFolder = await isFolderTreeitem(items[i]); - const isOpened = await isOpenedFolderTreeitem(items[i]); - const isTopLevel = isFolder ? - await isTopLevelFolder(t, items[i]) : - false; - - await items[i].sendKeys(Key.ARROW_LEFT); - - // If the item is a folder and the folder was opened, arrow will close folder - if (isFolder && isOpened) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should close the folder' - ); - - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should not move the focus' - ); - // Send one more arrow key to the folder that is now closed - continue; - } - - // If the item is a top level folder and closed, arrow will do nothing - else if (isTopLevel) { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to document in top level folder at treeitem index ' + i + - ' should not move focus' - ); - } - - // If the item is a document in folder, or a closed folder, arrow will move up a folder - else { - t.true( - await checkFocusOnParentFolder(t, items[i]), - 'Sending key ARROW_LEFT to document in folder at treeitem index ' + i + - ' should move focus to parent folder' - ); +); + +ariaTest( + 'key left arrow closes folders and moves focus', + exampleFile, + 'key-left-arrow', + async (t) => { + // Open all folders + await openAllFolders(t); - t.is( - await items[i].isDisplayed(), - true, - 'Sending key ARROW_LEFT to document in folder at treeitem index ' + i + - ' should not close the folder it is in' - ); + const items = await t.context.queryElements(t, ex.treeitemSelector); + + let i = items.length - 1; + while (i > 0) { + const isFolder = await isFolderTreeitem(items[i]); + const isOpened = await isOpenedFolderTreeitem(items[i]); + const isTopLevel = isFolder ? await isTopLevelFolder(t, items[i]) : false; + + await items[i].sendKeys(Key.ARROW_LEFT); + + // If the item is a folder and the folder was opened, arrow will close folder + if (isFolder && isOpened) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should close the folder' + ); + + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should not move the focus' + ); + // Send one more arrow key to the folder that is now closed + continue; + } + + // If the item is a top level folder and closed, arrow will do nothing + else if (isTopLevel) { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to document in top level folder at treeitem index ' + + i + + ' should not move focus' + ); + } + + // If the item is a document in folder, or a closed folder, arrow will move up a folder + else { + t.true( + await checkFocusOnParentFolder(t, items[i]), + 'Sending key ARROW_LEFT to document in folder at treeitem index ' + + i + + ' should move focus to parent folder' + ); + + t.is( + await items[i].isDisplayed(), + true, + 'Sending key ARROW_LEFT to document in folder at treeitem index ' + + i + + ' should not close the folder it is in' + ); + } + + i--; } - - i--; } -}); +); ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { - // Test that key "home" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.topLevelFolderSelector, 0), - 'Sending key HOME to top level folder at index ' + i + ' should move focus to first top level folder' + await checkFocus(t, ex.topLevelFolderSelector, 0), + 'Sending key HOME to top level folder at index ' + + i + + ' should move focus to first top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key HOME to top level folder at index ' + i + ' should not expand the folder' + 'Sending key HOME to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(t.context.url); @@ -491,37 +560,48 @@ ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.treeitemSelector, 0), - 'Sending key HOME to folder/item at index ' + i + ' will move focus to the first item' + await checkFocus(t, ex.treeitemSelector, 0), + 'Sending key HOME to folder/item at index ' + + i + + ' will move focus to the first item' ); } }); ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { - // Test that key "end" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.topLevelFolderSelector, topLevelFolders.length - 1), - 'Sending key END to top level folder at index ' + i + ' should move focus to last top level folder' + await checkFocus( + t, + ex.topLevelFolderSelector, + topLevelFolders.length - 1 + ), + 'Sending key END to top level folder at index ' + + i + + ' should move focus to last top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key END to top level folder at index ' + i + ' should not expand the folder' + 'Sending key END to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(t.context.url); @@ -530,23 +610,23 @@ ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.treeitemSelector, (items.length - 1)), - 'Sending key END to folder/item at index ' + i + + await checkFocus(t, ex.treeitemSelector, items.length - 1), + 'Sending key END to folder/item at index ' + + i + ' will move focus to the last item in the last opened folder' ); } }); ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { - const charIndexTestClosed = [ { sendChar: 'p', sendIndex: 0, endIndex: 0 }, { sendChar: 'r', sendIndex: 0, endIndex: 1 }, - { sendChar: 'l', sendIndex: 1, endIndex: 2 } + { sendChar: 'l', sendIndex: 1, endIndex: 2 }, ]; const charIndexTestOpened = [ @@ -558,23 +638,35 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { { sendChar: 'r', sendIndex: 3, endIndex: 15 }, { sendChar: 'r', sendIndex: 15, endIndex: 16 }, { sendChar: 'l', sendIndex: 3, endIndex: 30 }, - { sendChar: 'l', sendIndex: 30, endIndex: 31 } + { sendChar: 'l', sendIndex: 30, endIndex: 31 }, ]; - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); for (let test of charIndexTestClosed) { - // Send character to treeitem await topLevelFolders[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate item t.true( await checkFocus(t, ex.topLevelFolderSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'false' + ); } // Reload page @@ -586,64 +678,99 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); for (let test of charIndexTestOpened) { - // Send character to treeitem await items[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate treeitem t.true( await checkFocus(t, ex.treeitemSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); } }); -ariaTest('asterisk key opens folders', exampleFile, 'key-asterisk', async (t) => { - - /* Test that "*" ONLY opens all top level nodes and no other folders */ +ariaTest( + 'asterisk key opens folders', + exampleFile, + 'key-asterisk', + async (t) => { + /* Test that "*" ONLY opens all top level nodes and no other folders */ - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - const nextLevelFolders = await t.context.queryElements(t, ex.nextLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); + const nextLevelFolders = await t.context.queryElements( + t, + ex.nextLevelFolderSelector + ); - // Send Key - await topLevelFolders[0].sendKeys('*'); + // Send Key + await topLevelFolders[0].sendKeys('*'); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'true'); - await assertAttributeValues(t, ex.nextLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'true' + ); + await assertAttributeValues( + t, + ex.nextLevelFolderSelector, + 'aria-expanded', + 'false' + ); - /* Test that "*" ONLY opens sibling folders at that level */ + /* Test that "*" ONLY opens sibling folders at that level */ - // Send key - await nextLevelFolders[0].sendKeys('*'); + // Send key + await nextLevelFolders[0].sendKeys('*'); - // The subfolders of first top level folder should all be open + // The subfolders of first top level folder should all be open - const subFoldersOfFirstFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[0]); - for (let el of subFoldersOfFirstFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'true', - 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + const subFoldersOfFirstFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[0] ); - } + for (let el of subFoldersOfFirstFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'true', + 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of second top level folder should all be closed + // The subfolders of second top level folder should all be closed - const subFoldersOfSecondFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[1]); - for (let el of subFoldersOfSecondFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfSecondFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[1] ); - } + for (let el of subFoldersOfSecondFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of third top level folder should all be closed + // The subfolders of third top level folder should all be closed - const subFoldersOfThirdFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[2]); - for (let el of subFoldersOfThirdFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfThirdFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[2] ); + for (let el of subFoldersOfThirdFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } } - -}); +); diff --git a/test/tests/treeview_treeview-1b.js b/test/tests/treeview_treeview-1b.js index 56d618baf7..7a63271ad4 100644 --- a/test/tests/treeview_treeview-1b.js +++ b/test/tests/treeview_treeview-1b.js @@ -20,7 +20,7 @@ const ex = { treeitemGroupSelectors: { 1: [ // Top level folders - '[role="tree"]>[role="treeitem"]' + '[role="tree"]>[role="treeitem"]', ], 2: [ // Content of first top level folder @@ -30,7 +30,7 @@ const ex = { '[role="tree"]>[role="treeitem"]:nth-of-type(2)>[role="group"]>[role="treeitem"]', // Content of third top level folder - '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]' + '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]', ], 3: [ @@ -46,9 +46,9 @@ const ex = { // Content of subfolders of third top level folder '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(1) [role="treeitem"]', '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(2) [role="treeitem"]', - '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(3) [role="treeitem"]' - ] - } + '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(3) [role="treeitem"]', + ], + }, }; const openAllFolders = async function (t) { @@ -63,11 +63,15 @@ const openAllFolders = async function (t) { }; const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function (/* selector, index*/) { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function (/* selector, index*/) { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const checkFocusOnParentFolder = async function (t, el) { @@ -76,11 +80,17 @@ const checkFocusOnParentFolder = async function (t, el) { // the element is a folder if (el.hasAttribute('aria-expanded')) { - return document.activeElement === el.parentElement.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.parentElement.closest('[role="treeitem"][aria-expanded]') + ); } // the element is a folder else { - return document.activeElement === el.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.closest('[role="treeitem"][aria-expanded]') + ); } }, el); }; @@ -96,17 +106,15 @@ const isFolderTreeitem = async function (el) { return !(await el.getAttribute('class')).includes('doc'); }; -const isOpenedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'true'; +const isOpenedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'true'; }; -const isClosedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'false'; +const isClosedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'false'; }; ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { - - const trees = await t.context.queryElements(t, ex.treeSelector); t.is( @@ -122,293 +130,360 @@ ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { ); }); -ariaTest('aria-labelledby on role="tree" element', exampleFile, 'tree-arialabelledby', async (t) => { - - - await assertAriaLabelledby(t, ex.treeSelector); -}); - -ariaTest('role="treeitem" on "ul" element', exampleFile, 'treeitem-role', async (t) => { - - - const treeitems = await t.context.queryElements(t, ex.treeitemSelector); - - t.truthy( - treeitems.length, - 'role="treeitem" elements should be found by selector: ' + ex.treeitemSelector - ); - - for (let treeitem of treeitems) { - t.is( - await treeitem.getTagName(), - 'li', - 'role="treeitem" should be found on a "li"' +ariaTest( + 'aria-labelledby on role="tree" element', + exampleFile, + 'tree-arialabelledby', + async (t) => { + await assertAriaLabelledby(t, ex.treeSelector); + } +); + +ariaTest( + 'role="treeitem" on "ul" element', + exampleFile, + 'treeitem-role', + async (t) => { + const treeitems = await t.context.queryElements(t, ex.treeitemSelector); + + t.truthy( + treeitems.length, + 'role="treeitem" elements should be found by selector: ' + + ex.treeitemSelector ); + + for (let treeitem of treeitems) { + t.is( + await treeitem.getTagName(), + 'li', + 'role="treeitem" should be found on a "li"' + ); + } } -}); +); -ariaTest('treeitem tabindex set by roving tabindex', exampleFile, 'treeitem-tabindex', async (t) => { +ariaTest( + 'treeitem tabindex set by roving tabindex', + exampleFile, + 'treeitem-tabindex', + async (t) => { await openAllFolders(t); - await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); -}); - -ariaTest('"aria-setsize" atrribute on treeitem', exampleFile, 'treeitem-aria-setsize', async (t) => { - - for (const [level, levelSelectors] of Object.entries(ex.treeitemGroupSelectors)) { - for (const selector of levelSelectors) { - - const treeitems = await t.context.queryElements(t, selector); - const setsize = treeitems.length; - - for (const treeitem of treeitems) { - t.is( - await treeitem.getAttribute('aria-setsize'), - setsize.toString(), - '"aria-setsize" attribute should be set to group size (' + setsize + ') in group "' + selector + '"' - ); + await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); + } +); + +ariaTest( + '"aria-setsize" atrribute on treeitem', + exampleFile, + 'treeitem-aria-setsize', + async (t) => { + for (const [level, levelSelectors] of Object.entries( + ex.treeitemGroupSelectors + )) { + for (const selector of levelSelectors) { + const treeitems = await t.context.queryElements(t, selector); + const setsize = treeitems.length; + + for (const treeitem of treeitems) { + t.is( + await treeitem.getAttribute('aria-setsize'), + setsize.toString(), + '"aria-setsize" attribute should be set to group size (' + + setsize + + ') in group "' + + selector + + '"' + ); + } } } } +); + +ariaTest( + '"aria-posinset" attribute on treeitem', + exampleFile, + 'treeitem-aria-posinset', + async (t) => { + for (const [level, levelSelectors] of Object.entries( + ex.treeitemGroupSelectors + )) { + for (const selector of levelSelectors) { + const treeitems = await t.context.queryElements(t, selector); + let pos = 0; + for (const treeitem of treeitems) { + pos++; + t.is( + await treeitem.getAttribute('aria-posinset'), + pos.toString(), + '"aria-posinset" attribute should be set to "' + + pos + + '" for treeitem in group "' + + selector + + '"' + ); + } + } + } + } +); + +ariaTest( + '"aria-level" attribute on treeitem', + exampleFile, + 'treeitem-aria-level', + async (t) => { + for (const [level, levelSelectors] of Object.entries( + ex.treeitemGroupSelectors + )) { + for (const selector of levelSelectors) { + const treeitems = await t.context.queryElements(t, selector); + for (const treeitem of treeitems) { + t.is( + await treeitem.getAttribute('aria-level'), + level.toString(), + '"aria-level" attribute should be set to level "' + + level + + '" in group "' + + selector + + '"' + ); + } + } + } + } +); + +ariaTest( + 'aria-expanded attribute on treeitem matches dom', + exampleFile, + 'treeitem-aria-expanded', + async (t) => { + const folders = await t.context.queryElements(t, ex.folderSelector); + + for (let folder of folders) { + // If the folder is displayed + if (await folder.isDisplayed()) { + const folderText = await folder.getText(); + + // By default, all folders will be closed + t.is(await folder.getAttribute('aria-expanded'), 'false'); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + false + ); -}); - -ariaTest('"aria-posinset" attribute on treeitem', exampleFile, 'treeitem-aria-posinset', async (t) => { - - for (const [level, levelSelectors] of Object.entries(ex.treeitemGroupSelectors)) { - for (const selector of levelSelectors) { + // Send enter to the folder + await folder.sendKeys(Key.ENTER); - const treeitems = await t.context.queryElements(t, selector); - let pos = 0; - for (const treeitem of treeitems) { - pos++; + // After click, it should be open + t.is(await folder.getAttribute('aria-expanded'), 'true'); t.is( - await treeitem.getAttribute('aria-posinset'), - pos.toString(), - '"aria-posinset" attribute should be set to "' + pos + '" for treeitem in group "' + selector + '"' + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + true ); } } - } -}); -ariaTest('"aria-level" attribute on treeitem', exampleFile, 'treeitem-aria-level', async (t) => { - - for (const [level, levelSelectors] of Object.entries(ex.treeitemGroupSelectors)) { - for (const selector of levelSelectors) { + for (let i = folders.length - 1; i >= 0; i--) { + // If the folder is displayed + if (await folders[i].isDisplayed()) { + const folderText = await folders[i].getText(); + + // Send enter to the folder + await folders[i].sendKeys(Key.ENTER); - const treeitems = await t.context.queryElements(t, selector); - for (const treeitem of treeitems) { + // After sending enter, it should be closed t.is( - await treeitem.getAttribute('aria-level'), - level.toString(), - '"aria-level" attribute should be set to level "' + level + '" in group "' + selector + '"' + await folders[i].getAttribute('aria-expanded'), + 'false', + folderText + ); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folders[i]) + ).isDisplayed(), + false, + folderText ); } } } -}); - -ariaTest('aria-expanded attribute on treeitem matches dom', exampleFile, 'treeitem-aria-expanded', async (t) => { +); + +ariaTest( + 'role="group" on "ul" elements', + exampleFile, + 'role-group', + async (t) => { + const groups = await t.context.queryElements(t, ex.groupSelector); + + t.truthy( + groups.length, + 'role="group" elements should be found by selector: ' + ex.groupSelector + ); - - const folders = await t.context.queryElements(t, ex.folderSelector); + for (let group of groups) { + t.is( + await group.getTagName(), + 'ul', + 'role="group" should be found on a "ul"' + ); + } + } +); - for (let folder of folders) { +// Keys - // If the folder is displayed - if (await folder.isDisplayed()) { +ariaTest( + 'Key enter opens folder', + exampleFile, + 'key-enter-or-space', + async (t) => { + await openAllFolders(t); - const folderText = await folder.getText(); + const items = await t.context.queryElements(t, ex.docSelector); - // By default, all folders will be closed - t.is( - await folder.getAttribute('aria-expanded'), - 'false' - ); - t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - false - ); + for (let item of items) { + const itemText = await item.getText(); // Send enter to the folder - await folder.sendKeys(Key.ENTER); + await item.sendKeys(Key.ENTER); - // After click, it should be open - t.is( - await folder.getAttribute('aria-expanded'), - 'true' - ); + const boxText = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('value'); t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - true + boxText, + itemText, + 'Sending enter to doc "' + + itemText + + '" should update the value in the textbox' ); } } +); - for (let i = (folders.length - 1); i >= 0; i--) { +ariaTest( + 'Key space opens folder', + exampleFile, + 'key-enter-or-space', + async (t) => { + await openAllFolders(t); - // If the folder is displayed - if (await folders[i].isDisplayed()) { + const items = await t.context.queryElements(t, ex.docSelector); - const folderText = await folders[i].getText(); + for (let item of items) { + const itemText = await item.getText(); // Send enter to the folder - await folders[i].sendKeys(Key.ENTER); + await item.sendKeys(Key.SPACE); - // After sending enter, it should be closed - t.is( - await folders[i].getAttribute('aria-expanded'), - 'false', - folderText - ); + const boxText = await t.context.session + .findElement(By.css(ex.textboxSelector)) + .getAttribute('value'); t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folders[i])).isDisplayed(), - false, - folderText + boxText, + itemText, + 'Sending space to doc "' + + itemText + + '" should update the value in the textbox' ); } } - -}); - -ariaTest('role="group" on "ul" elements', exampleFile, 'role-group', async (t) => { - - - const groups = await t.context.queryElements(t, ex.groupSelector); - - t.truthy( - groups.length, - 'role="group" elements should be found by selector: ' + ex.groupSelector - ); - - for (let group of groups) { - t.is( - await group.getTagName(), - 'ul', - 'role="group" should be found on a "ul"' +); + +ariaTest( + 'key down arrow moves focus', + exampleFile, + 'key-down-arrow', + async (t) => { + // Check that the down arrow does not open folders + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector ); - } -}); -// Keys - -ariaTest('Key enter opens folder', exampleFile, 'key-enter-or-space', async (t) => { - - await openAllFolders(t); + for (let i = 0; i < topLevelFolders.length; i++) { + await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.docSelector); + // If we are on the last top level folder, the focus will not move + const nextIndex = i === topLevelFolders.length - 1 ? i : i + 1; - for (let item of items) { - - const itemText = await item.getText(); - - // Send enter to the folder - await item.sendKeys(Key.ENTER); - - const boxText = await t.context.session.findElement(By.css(ex.textboxSelector)).getAttribute('value'); - t.is( - boxText, - itemText, - 'Sending enter to doc "' + itemText + '" should update the value in the textbox' - ); - } - -}); - -ariaTest('Key space opens folder', exampleFile, 'key-enter-or-space', async (t) => { - - await openAllFolders(t); - - const items = await t.context.queryElements(t, ex.docSelector); - - for (let item of items) { - - const itemText = await item.getText(); - - // Send enter to the folder - await item.sendKeys(Key.SPACE); - - const boxText = await t.context.session.findElement(By.css(ex.textboxSelector)).getAttribute('value'); - t.is( - boxText, - itemText, - 'Sending space to doc "' + itemText + '" should update the value in the textbox' - ); - } - -}); - -ariaTest('key down arrow moves focus', exampleFile, 'key-down-arrow', async (t) => { - - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - - for (let i = 0; i < topLevelFolders.length; i++) { - await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); - - // If we are on the last top level folder, the focus will not move - const nextIndex = i === topLevelFolders.length - 1 ? - i : - i + 1; - - t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex + ); - t.is( - await topLevelFolders[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' should not expand the folder' - ); - } + t.is( + await topLevelFolders[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' should not expand the folder' + ); + } - // Reload page - await t.context.session.get(await t.context.session.getCurrentUrl()); + // Reload page + await t.context.session.get(await t.context.session.getCurrentUrl()); - // Open all folders - await openAllFolders(t); + // Open all folders + await openAllFolders(t); - const items = await t.context.queryElements(t, ex.treeitemSelector); + const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = 0; i < items.length; i++) { - await items[i].sendKeys(Key.ARROW_DOWN); + for (let i = 0; i < items.length; i++) { + await items[i].sendKeys(Key.ARROW_DOWN); - // If we are on the last item, the focus will not move - const nextIndex = i === items.length - 1 ? - i : - i + 1; + // If we are on the last item, the focus will not move + const nextIndex = i === items.length - 1 ? i : i + 1; - t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_DOWN to top level folder/item at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_DOWN to top level folder/item at index ' + + i + + ' will move focus to ' + + nextIndex + ); + } } -}); +); ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.ARROW_UP); // If we are on the last top level folder, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_UP to top level folder at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key ARROW_UP to top level folder at index ' + i + ' should not expand the folder' + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' should not expand the folder' ); } @@ -420,157 +495,177 @@ ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.ARROW_UP); // If we are on the last item, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_UP to top level folder/item at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_UP to top level folder/item at index ' + + i + + ' will move focus to ' + + nextIndex ); } }); -ariaTest('key right arrow opens folders and moves focus', exampleFile, 'key-right-arrow', async (t) => { - - const items = await t.context.queryElements(t, ex.treeitemSelector); - - let i = 0; - while (i < items.length) { +ariaTest( + 'key right arrow opens folders and moves focus', + exampleFile, + 'key-right-arrow', + async (t) => { + const items = await t.context.queryElements(t, ex.treeitemSelector); - const isFolder = await isFolderTreeitem(items[i]); - const isClosed = await isClosedFolderTreeitem(items[i]); + let i = 0; + while (i < items.length) { + const isFolder = await isFolderTreeitem(items[i]); + const isClosed = await isClosedFolderTreeitem(items[i]); - await items[i].sendKeys(Key.ARROW_RIGHT); + await items[i].sendKeys(Key.ARROW_RIGHT); - // If the item is a folder and it was originally closed - if (isFolder && isClosed) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'true', - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder is closed should open the folder' - ); - - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder was closed should not move the focus' - ); - continue; - } + // If the item is a folder and it was originally closed + if (isFolder && isClosed) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'true', + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder is closed should open the folder' + ); - // If the folder is an open folder, the focus will move - else if (isFolder) { - t.true( - await checkFocus(t, ex.treeitemSelector, i + 1), - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' should move focus to item ' + (i + 1) - ); - } + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder was closed should not move the focus' + ); + continue; + } - // If we are a document, the focus will not move - else { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to document item at treeitem index ' + i + ' should not move focus' - ); + // If the folder is an open folder, the focus will move + else if (isFolder) { + t.true( + await checkFocus(t, ex.treeitemSelector, i + 1), + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' should move focus to item ' + + (i + 1) + ); + } + // If we are a document, the focus will not move + else { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to document item at treeitem index ' + + i + + ' should not move focus' + ); + } + i++; } - i++; } -}); - -ariaTest('key left arrow closes folders and moves focus', exampleFile, 'key-left-arrow', async (t) => { - - // Open all folders - await openAllFolders(t); +); + +ariaTest( + 'key left arrow closes folders and moves focus', + exampleFile, + 'key-left-arrow', + async (t) => { + // Open all folders + await openAllFolders(t); - const items = await t.context.queryElements(t, ex.treeitemSelector); + const items = await t.context.queryElements(t, ex.treeitemSelector); - let i = items.length - 1; - while (i > 0) { + let i = items.length - 1; + while (i > 0) { + const isFolder = await isFolderTreeitem(items[i]); + const isOpened = await isOpenedFolderTreeitem(items[i]); + const isTopLevel = isFolder ? await isTopLevelFolder(t, items[i]) : false; - const isFolder = await isFolderTreeitem(items[i]); - const isOpened = await isOpenedFolderTreeitem(items[i]); - const isTopLevel = isFolder ? - await isTopLevelFolder(t, items[i]) : - false; + await items[i].sendKeys(Key.ARROW_LEFT); - await items[i].sendKeys(Key.ARROW_LEFT); + // If the item is a folder and the folder was opened, arrow will close folder + if (isFolder && isOpened) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should close the folder' + ); - // If the item is a folder and the folder was opened, arrow will close folder - if (isFolder && isOpened) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should close the folder' - ); + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should not move the focus' + ); + // Send one more arrow key to the folder that is now closed + continue; + } - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should not move the focus' - ); - // Send one more arrow key to the folder that is now closed - continue; - } + // If the item is a top level folder and closed, arrow will do nothing + else if (isTopLevel) { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to document in top level folder at treeitem index ' + + i + + ' should not move focus' + ); + } - // If the item is a top level folder and closed, arrow will do nothing - else if (isTopLevel) { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to document in top level folder at treeitem index ' + i + - ' should not move focus' - ); - } + // If the item is a document in folder, or a closed folder, arrow will move up a folder + else { + t.true( + await checkFocusOnParentFolder(t, items[i]), + 'Sending key ARROW_LEFT to document in folder at treeitem index ' + + i + + ' should move focus to parent folder' + ); - // If the item is a document in folder, or a closed folder, arrow will move up a folder - else { - t.true( - await checkFocusOnParentFolder(t, items[i]), - 'Sending key ARROW_LEFT to document in folder at treeitem index ' + i + - ' should move focus to parent folder' - ); + t.is( + await items[i].isDisplayed(), + true, + 'Sending key ARROW_LEFT to document in folder at treeitem index ' + + i + + ' should not close the folder it is in' + ); + } - t.is( - await items[i].isDisplayed(), - true, - 'Sending key ARROW_LEFT to document in folder at treeitem index ' + i + - ' should not close the folder it is in' - ); + i--; } - - i--; } -}); +); ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { - // Test that key "home" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.topLevelFolderSelector, 0), - 'Sending key HOME to top level folder at index ' + i + ' should move focus to first top level folder' + await checkFocus(t, ex.topLevelFolderSelector, 0), + 'Sending key HOME to top level folder at index ' + + i + + ' should move focus to first top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key HOME to top level folder at index ' + i + ' should not expand the folder' + 'Sending key HOME to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(await t.context.session.getCurrentUrl()); @@ -579,37 +674,48 @@ ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.treeitemSelector, 0), - 'Sending key HOME to top level folder/item at index ' + i + ' will move focus to the first item' + await checkFocus(t, ex.treeitemSelector, 0), + 'Sending key HOME to top level folder/item at index ' + + i + + ' will move focus to the first item' ); } }); ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { - // Test that key "end" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.topLevelFolderSelector, topLevelFolders.length - 1), - 'Sending key END to top level folder at index ' + i + ' should move focus to last top level folder' + await checkFocus( + t, + ex.topLevelFolderSelector, + topLevelFolders.length - 1 + ), + 'Sending key END to top level folder at index ' + + i + + ' should move focus to last top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key END to top level folder at index ' + i + ' should not expand the folder' + 'Sending key END to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(await t.context.session.getCurrentUrl()); @@ -618,23 +724,23 @@ ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.treeitemSelector, (items.length - 1)), - 'Sending key END to top level folder/item at index ' + i + + await checkFocus(t, ex.treeitemSelector, items.length - 1), + 'Sending key END to top level folder/item at index ' + + i + ' will move focus to the last item in the last opened folder' ); } }); ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { - const charIndexTestClosed = [ { sendChar: 'p', sendIndex: 0, endIndex: 0 }, { sendChar: 'r', sendIndex: 0, endIndex: 1 }, - { sendChar: 'l', sendIndex: 1, endIndex: 2 } + { sendChar: 'l', sendIndex: 1, endIndex: 2 }, ]; const charIndexTestOpened = [ @@ -646,23 +752,35 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { { sendChar: 'r', sendIndex: 3, endIndex: 15 }, { sendChar: 'r', sendIndex: 15, endIndex: 16 }, { sendChar: 'l', sendIndex: 3, endIndex: 30 }, - { sendChar: 'l', sendIndex: 30, endIndex: 31 } + { sendChar: 'l', sendIndex: 30, endIndex: 31 }, ]; - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); for (let test of charIndexTestClosed) { - // Send character to treeitem await topLevelFolders[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate item t.true( await checkFocus(t, ex.topLevelFolderSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'false' + ); } // Reload page @@ -674,64 +792,99 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); for (let test of charIndexTestOpened) { - // Send character to treeitem await items[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate treeitem t.true( await checkFocus(t, ex.treeitemSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); } }); -ariaTest('asterisk key opens folders', exampleFile, 'key-asterisk', async (t) => { - - /* Test that "*" ONLY opens all top level nodes and no other folders */ +ariaTest( + 'asterisk key opens folders', + exampleFile, + 'key-asterisk', + async (t) => { + /* Test that "*" ONLY opens all top level nodes and no other folders */ - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - const nextLevelFolders = await t.context.queryElements(t, ex.nextLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); + const nextLevelFolders = await t.context.queryElements( + t, + ex.nextLevelFolderSelector + ); - // Send Key - await topLevelFolders[0].sendKeys('*'); + // Send Key + await topLevelFolders[0].sendKeys('*'); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'true'); - await assertAttributeValues(t, ex.nextLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'true' + ); + await assertAttributeValues( + t, + ex.nextLevelFolderSelector, + 'aria-expanded', + 'false' + ); - /* Test that "*" ONLY opens sibling folders at that level */ + /* Test that "*" ONLY opens sibling folders at that level */ - // Send key - await nextLevelFolders[0].sendKeys('*'); + // Send key + await nextLevelFolders[0].sendKeys('*'); - // The subfolders of first top level folder should all be open + // The subfolders of first top level folder should all be open - const subFoldersOfFirstFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[0]); - for (let el of subFoldersOfFirstFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'true', - 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + const subFoldersOfFirstFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[0] ); - } + for (let el of subFoldersOfFirstFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'true', + 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of second top level folder should all be closed + // The subfolders of second top level folder should all be closed - const subFoldersOfSecondFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[1]); - for (let el of subFoldersOfSecondFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfSecondFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[1] ); - } + for (let el of subFoldersOfSecondFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of third top level folder should all be closed + // The subfolders of third top level folder should all be closed - const subFoldersOfThirdFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[2]); - for (let el of subFoldersOfThirdFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfThirdFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[2] ); + for (let el of subFoldersOfThirdFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } } - -}); +); diff --git a/test/tests/treeview_treeview-2a.js b/test/tests/treeview_treeview-2a.js index e461ffb985..bc27e8f976 100644 --- a/test/tests/treeview_treeview-2a.js +++ b/test/tests/treeview_treeview-2a.js @@ -15,7 +15,7 @@ const ex = { folderSelector: '#ex1 [role="treeitem"][aria-expanded]', topLevelFolderSelector: '#ex1 [role="tree"] > [role="treeitem"]', nextLevelFolderSelector: '[role="group"] > [role="treeitem"][aria-expanded]', - linkSelector: '#ex1 a[role="treeitem"]' + linkSelector: '#ex1 a[role="treeitem"]', }; const openAllFolders = async function (t) { @@ -30,11 +30,15 @@ const openAllFolders = async function (t) { }; const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function (/* selector, index*/) { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function (/* selector, index*/) { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const checkFocusOnParentFolder = async function (t, el) { @@ -43,11 +47,17 @@ const checkFocusOnParentFolder = async function (t, el) { // the element is a folder if (el.hasAttribute('aria-expanded')) { - return document.activeElement === el.parentElement.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.parentElement.closest('[role="treeitem"][aria-expanded]') + ); } // the element is a folder else { - return document.activeElement === el.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.closest('[role="treeitem"][aria-expanded]') + ); } }, el); }; @@ -63,12 +73,12 @@ const isFolderTreeitem = async function (el) { return (await el.getTagName()) === 'li'; }; -const isOpenedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'true'; +const isOpenedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'true'; }; -const isClosedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'false'; +const isClosedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'false'; }; const hasAriaExpandedAttribute = async function (t, el) { @@ -79,8 +89,6 @@ const hasAriaExpandedAttribute = async function (t, el) { }; ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { - - const trees = await t.context.queryElements(t, ex.treeSelector); t.is( @@ -96,45 +104,47 @@ ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { ); }); -ariaTest('aria-labelledby on role="tree" element', exampleFile, 'tree-aria-labelledby', async (t) => { - - - await assertAriaLabelledby(t, ex.treeSelector); -}); - -ariaTest('role="treeitem" on "li" or "a" element', exampleFile, 'treeitem-role', async (t) => { - - - // Get all the list items in the tree structure - const listitems = await t.context.queryElements(t, '#ex1 [role="tree"] li'); - - // Check the role "treeitem" is on the list item (in the case of a directory) or contained link - for (let item of listitems) { - - const hasAriaExpanded = await hasAriaExpandedAttribute(t, item); - - // if "aria-expanded" is contained on the list item, it is a directory - if (hasAriaExpanded) { - t.is( - await item.getAttribute('role'), - 'treeitem', - 'role="treeitem" should be found on a "li" items that have attribute "aria-expanded"' - ); - } - else { - const links = await t.context.queryElements(t, 'a', item); - t.is( - await links[0].getAttribute('role'), - 'treeitem', - 'role="treeitem" should be found on focusable "a" elements within tree structure' - ); +ariaTest( + 'aria-labelledby on role="tree" element', + exampleFile, + 'tree-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.treeSelector); + } +); + +ariaTest( + 'role="treeitem" on "li" or "a" element', + exampleFile, + 'treeitem-role', + async (t) => { + // Get all the list items in the tree structure + const listitems = await t.context.queryElements(t, '#ex1 [role="tree"] li'); + + // Check the role "treeitem" is on the list item (in the case of a directory) or contained link + for (let item of listitems) { + const hasAriaExpanded = await hasAriaExpandedAttribute(t, item); + + // if "aria-expanded" is contained on the list item, it is a directory + if (hasAriaExpanded) { + t.is( + await item.getAttribute('role'), + 'treeitem', + 'role="treeitem" should be found on a "li" items that have attribute "aria-expanded"' + ); + } else { + const links = await t.context.queryElements(t, 'a', item); + t.is( + await links[0].getAttribute('role'), + 'treeitem', + 'role="treeitem" should be found on focusable "a" elements within tree structure' + ); + } } } -}); +); ariaTest('role="none" on "li" element', exampleFile, 'none-role', async (t) => { - - // Get all the list items in the tree structure const listitems = await t.context.queryElements(t, '#ex1 [role="tree"] li'); @@ -152,229 +162,265 @@ ariaTest('role="none" on "li" element', exampleFile, 'none-role', async (t) => { } }); - -ariaTest('treeitem tabindex set by roving tabindex', exampleFile, 'treeitem-tabindex', async (t) => { +ariaTest( + 'treeitem tabindex set by roving tabindex', + exampleFile, + 'treeitem-tabindex', + async (t) => { await openAllFolders(t); - await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); -}); - -ariaTest('aria-expanded attribute on treeitem matches dom', exampleFile, 'treeitem-aria-expanded', async (t) => { - - - const folders = await t.context.queryElements(t, ex.folderSelector); - - for (let folder of folders) { - - // If the folder is displayed - if (await folder.isDisplayed()) { - - const folderText = await folder.getText(); - - // By default, all folders will be closed - t.is( - await folder.getAttribute('aria-expanded'), - 'false' - ); - t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - false - ); - - // Send enter to the folder - await folder.sendKeys(Key.ENTER); + await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); + } +); + +ariaTest( + 'aria-expanded attribute on treeitem matches dom', + exampleFile, + 'treeitem-aria-expanded', + async (t) => { + const folders = await t.context.queryElements(t, ex.folderSelector); + + for (let folder of folders) { + // If the folder is displayed + if (await folder.isDisplayed()) { + const folderText = await folder.getText(); + + // By default, all folders will be closed + t.is(await folder.getAttribute('aria-expanded'), 'false'); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + false + ); + + // Send enter to the folder + await folder.sendKeys(Key.ENTER); + + // After click, it should be open + t.is(await folder.getAttribute('aria-expanded'), 'true'); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + true + ); + } + } - // After click, it should be open - t.is( - await folder.getAttribute('aria-expanded'), - 'true' - ); - t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - true - ); + for (let i = folders.length - 1; i >= 0; i--) { + // If the folder is displayed + if (await folders[i].isDisplayed()) { + const folderText = await folders[i].getText(); + + // Send enter to the folder + await folders[i].sendKeys(Key.ENTER); + + // After sending enter, it should be closed + t.is( + await folders[i].getAttribute('aria-expanded'), + 'false', + folderText + ); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folders[i]) + ).isDisplayed(), + false, + folderText + ); + } } } +); + +ariaTest( + 'role="group" on "ul" elements', + exampleFile, + 'group-role', + async (t) => { + const groups = await t.context.queryElements(t, ex.groupSelector); + + t.truthy( + groups.length, + 'role="group" elements should be found by selector: ' + ex.groupSelector + ); - for (let i = (folders.length - 1); i >= 0; i--) { - - // If the folder is displayed - if (await folders[i].isDisplayed()) { - - const folderText = await folders[i].getText(); - - // Send enter to the folder - await folders[i].sendKeys(Key.ENTER); - - // After sending enter, it should be closed - t.is( - await folders[i].getAttribute('aria-expanded'), - 'false', - folderText - ); + for (let group of groups) { t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folders[i])).isDisplayed(), - false, - folderText + await group.getTagName(), + 'ul', + 'role="group" should be found on a "ul"' ); } } +); -}); - -ariaTest('role="group" on "ul" elements', exampleFile, 'group-role', async (t) => { +// Keys - - const groups = await t.context.queryElements(t, ex.groupSelector); +ariaTest( + 'Key enter opens folder and activates link', + exampleFile, + 'key-enter-or-space', + async (t) => { + let folders = await t.context.queryElements(t, ex.folderSelector); - t.truthy( - groups.length, - 'role="group" elements should be found by selector: ' + ex.groupSelector - ); + // Going through all closed folder elements in dom order will open parent + // folders first, therefore all child folders will be visible before sending "enter" + for (let folder of folders) { + await folder.sendKeys(Key.ENTER); + } - for (let group of groups) { - t.is( - await group.getTagName(), - 'ul', - 'role="group" should be found on a "ul"' + // Assert that the attribute value "aria-expanded" on all folders is "true" + await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); + + // Test a leaf node + let leafnodes = await t.context.queryElements(t, ex.linkSelector); + await leafnodes[0].sendKeys(Key.ENTER); + + await t.context.session + .wait(() => { + return t.context.session.getCurrentUrl().then((url) => { + return url != t.context.url; + }); + }, t.context.waitTime) + .catch(() => {}); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'ENTER key on first element found by selector "' + + ex.linkSelector + + '" should activate link.' ); } -}); - -// Keys - -ariaTest('Key enter opens folder and activates link', exampleFile, 'key-enter-or-space', async (t) => { - - let folders = await t.context.queryElements(t, ex.folderSelector); - - // Going through all closed folder elements in dom order will open parent - // folders first, therefore all child folders will be visible before sending "enter" - for (let folder of folders) { - await folder.sendKeys(Key.ENTER); - } - - // Assert that the attribute value "aria-expanded" on all folders is "true" - await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); - - // Test a leaf node - let leafnodes = await t.context.queryElements(t, ex.linkSelector); - await leafnodes[0].sendKeys(Key.ENTER); - - await t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime).catch(() => {}); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'ENTER key on first element found by selector "' + ex.linkSelector + '" should activate link.' - ); - -}); +); // This test fails due to bug #869. -ariaTest.failing('Key space opens folder and activates link', exampleFile, 'key-enter-or-space', async (t) => { - - let folders = await t.context.queryElements(t, ex.folderSelector); +ariaTest.failing( + 'Key space opens folder and activates link', + exampleFile, + 'key-enter-or-space', + async (t) => { + let folders = await t.context.queryElements(t, ex.folderSelector); + + // Going through all closed folder elements in dom order will open parent + // folders first, therefore all child folders will be visible before sending "enter" + for (let folder of folders) { + await folder.sendKeys(Key.SPACE); + } - // Going through all closed folder elements in dom order will open parent - // folders first, therefore all child folders will be visible before sending "enter" - for (let folder of folders) { - await folder.sendKeys(Key.SPACE); + // Assert that the attribute value "aria-expanded" on all folders is "true" + await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); + + // Test a leaf node + let leafnodes = await t.context.queryElements(t, ex.linkSelector); + await leafnodes[0].sendKeys(Key.SPACE); + + await t.context.session + .wait(() => { + return t.context.session.getCurrentUrl().then((url) => { + return url != t.context.url; + }); + }, t.context.waitTime) + .catch(() => {}); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'SPACE key on first element found by selector "' + + ex.linkSelector + + '" should activate link.' + ); } +); + +ariaTest( + 'key down arrow moves focus', + exampleFile, + 'key-down-arrow', + async (t) => { + // Check that the down arrow does not open folders + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - // Assert that the attribute value "aria-expanded" on all folders is "true" - await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); - - // Test a leaf node - let leafnodes = await t.context.queryElements(t, ex.linkSelector); - await leafnodes[0].sendKeys(Key.SPACE); - - await t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime).catch(() => {}); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'SPACE key on first element found by selector "' + ex.linkSelector + '" should activate link.' - ); -}); - -ariaTest('key down arrow moves focus', exampleFile, 'key-down-arrow', async (t) => { - - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - - for (let i = 0; i < topLevelFolders.length; i++) { - await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); + for (let i = 0; i < topLevelFolders.length; i++) { + await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); - // If we are on the last top level folder, the focus will not move - const nextIndex = i === topLevelFolders.length - 1 ? - i : - i + 1; + // If we are on the last top level folder, the focus will not move + const nextIndex = i === topLevelFolders.length - 1 ? i : i + 1; - t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex + ); - t.is( - await topLevelFolders[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' should not expand the folder' - ); - } + t.is( + await topLevelFolders[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' should not expand the folder' + ); + } - // Reload page - await t.context.session.get(await t.context.session.getCurrentUrl()); + // Reload page + await t.context.session.get(await t.context.session.getCurrentUrl()); - // Open all folders - await openAllFolders(t); + // Open all folders + await openAllFolders(t); - const items = await t.context.queryElements(t, ex.treeitemSelector); + const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = 0; i < items.length; i++) { - await items[i].sendKeys(Key.ARROW_DOWN); + for (let i = 0; i < items.length; i++) { + await items[i].sendKeys(Key.ARROW_DOWN); - // If we are on the last item, the focus will not move - const nextIndex = i === items.length - 1 ? - i : - i + 1; + // If we are on the last item, the focus will not move + const nextIndex = i === items.length - 1 ? i : i + 1; - t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_DOWN to folder/item at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_DOWN to folder/item at index ' + + i + + ' will move focus to ' + + nextIndex + ); + } } -}); +); ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.ARROW_UP); // If we are on the last top level folder, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_UP to top level folder at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key ARROW_UP to top level folder at index ' + i + ' should not expand the folder' + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' should not expand the folder' ); } @@ -386,158 +432,178 @@ ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.ARROW_UP); // If we are on the last item, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_UP to folder/item at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_UP to folder/item at index ' + + i + + ' will move focus to ' + + nextIndex ); } }); -ariaTest('key right arrow opens folders and moves focus', exampleFile, 'key-right-arrow', async (t) => { - - const items = await t.context.queryElements(t, ex.treeitemSelector); - - let i = 0; - while (i < items.length) { - - const isFolder = await isFolderTreeitem(items[i]); - const isClosed = await isClosedFolderTreeitem(items[i]); - - await items[i].sendKeys(Key.ARROW_RIGHT); - - // If the item is a folder and it was originally closed - if (isFolder && isClosed) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'true', - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder is closed should open the folder' - ); - - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder was closed should not move the focus' - ); - continue; - } - - // If the folder is an open folder, the focus will move - else if (isFolder) { - t.true( - await checkFocus(t, ex.treeitemSelector, i + 1), - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' should move focus to item ' + (i + 1) - ); - } - - // If we are a link, the focus will not move - else { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to link item at treeitem index ' + i + ' should not move focus' - ); - +ariaTest( + 'key right arrow opens folders and moves focus', + exampleFile, + 'key-right-arrow', + async (t) => { + const items = await t.context.queryElements(t, ex.treeitemSelector); + + let i = 0; + while (i < items.length) { + const isFolder = await isFolderTreeitem(items[i]); + const isClosed = await isClosedFolderTreeitem(items[i]); + + await items[i].sendKeys(Key.ARROW_RIGHT); + + // If the item is a folder and it was originally closed + if (isFolder && isClosed) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'true', + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder is closed should open the folder' + ); + + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder was closed should not move the focus' + ); + continue; + } + + // If the folder is an open folder, the focus will move + else if (isFolder) { + t.true( + await checkFocus(t, ex.treeitemSelector, i + 1), + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' should move focus to item ' + + (i + 1) + ); + } + + // If we are a link, the focus will not move + else { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to link item at treeitem index ' + + i + + ' should not move focus' + ); + } + i++; } - i++; } -}); +); // This test fails due to bug #866. -ariaTest.failing('key left arrow closes folders and moves focus', exampleFile, 'key-left-arrow', async (t) => { - - // Open all folders - await openAllFolders(t); - - const items = await t.context.queryElements(t, ex.treeitemSelector); - - let i = items.length - 1; - while (i > 0) { - - const isFolder = await isFolderTreeitem(items[i]); - const isOpened = await isOpenedFolderTreeitem(items[i]); - const isTopLevel = isFolder ? - await isTopLevelFolder(t, items[i]) : - false; - - await items[i].sendKeys(Key.ARROW_LEFT); - - // If the item is a folder and the folder was opened, arrow will close folder - if (isFolder && isOpened) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should close the folder' - ); - - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should not move the focus' - ); - // Send one more arrow key to the folder that is now closed - continue; - } - - // If the item is a top level folder and closed, arrow will do nothing - else if (isTopLevel) { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to link in top level folder at treeitem index ' + i + - ' should not move focus' - ); - } - - // If the item is a link in folder, or a closed folder, arrow will move up a folder - else { - t.true( - await checkFocusOnParentFolder(t, items[i]), - 'Sending key ARROW_LEFT to link in folder at treeitem index ' + i + - ' should move focus to parent folder' - ); +ariaTest.failing( + 'key left arrow closes folders and moves focus', + exampleFile, + 'key-left-arrow', + async (t) => { + // Open all folders + await openAllFolders(t); - t.is( - await items[i].isDisplayed(), - true, - 'Sending key ARROW_LEFT to link in folder at treeitem index ' + i + - ' should not close the folder it is in' - ); + const items = await t.context.queryElements(t, ex.treeitemSelector); + + let i = items.length - 1; + while (i > 0) { + const isFolder = await isFolderTreeitem(items[i]); + const isOpened = await isOpenedFolderTreeitem(items[i]); + const isTopLevel = isFolder ? await isTopLevelFolder(t, items[i]) : false; + + await items[i].sendKeys(Key.ARROW_LEFT); + + // If the item is a folder and the folder was opened, arrow will close folder + if (isFolder && isOpened) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should close the folder' + ); + + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should not move the focus' + ); + // Send one more arrow key to the folder that is now closed + continue; + } + + // If the item is a top level folder and closed, arrow will do nothing + else if (isTopLevel) { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to link in top level folder at treeitem index ' + + i + + ' should not move focus' + ); + } + + // If the item is a link in folder, or a closed folder, arrow will move up a folder + else { + t.true( + await checkFocusOnParentFolder(t, items[i]), + 'Sending key ARROW_LEFT to link in folder at treeitem index ' + + i + + ' should move focus to parent folder' + ); + + t.is( + await items[i].isDisplayed(), + true, + 'Sending key ARROW_LEFT to link in folder at treeitem index ' + + i + + ' should not close the folder it is in' + ); + } + + i--; } - - i--; } -}); +); ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { - // Test that key "home" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.topLevelFolderSelector, 0), - 'Sending key HOME to top level folder at index ' + i + ' should move focus to first top level folder' + await checkFocus(t, ex.topLevelFolderSelector, 0), + 'Sending key HOME to top level folder at index ' + + i + + ' should move focus to first top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key HOME to top level folder at index ' + i + ' should not expand the folder' + 'Sending key HOME to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(await t.context.session.getCurrentUrl()); @@ -546,37 +612,48 @@ ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.treeitemSelector, 0), - 'Sending key HOME to top level folder/item at index ' + i + ' will move focus to the first item' + await checkFocus(t, ex.treeitemSelector, 0), + 'Sending key HOME to top level folder/item at index ' + + i + + ' will move focus to the first item' ); } }); ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { - // Test that key "end" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.topLevelFolderSelector, topLevelFolders.length - 1), - 'Sending key END to top level folder at index ' + i + ' should move focus to last top level folder' + await checkFocus( + t, + ex.topLevelFolderSelector, + topLevelFolders.length - 1 + ), + 'Sending key END to top level folder at index ' + + i + + ' should move focus to last top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key END to top level folder at index ' + i + ' should not expand the folder' + 'Sending key END to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(await t.context.session.getCurrentUrl()); @@ -585,23 +662,23 @@ ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.treeitemSelector, (items.length - 1)), - 'Sending key END to top level folder/item at index ' + i + + await checkFocus(t, ex.treeitemSelector, items.length - 1), + 'Sending key END to top level folder/item at index ' + + i + ' will move focus to the last item in the last opened folder' ); } }); ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { - const charIndexTestClosed = [ { sendChar: 'g', sendIndex: 0, endIndex: 2 }, { sendChar: 'f', sendIndex: 2, endIndex: 0 }, - { sendChar: 'v', sendIndex: 0, endIndex: 1 } + { sendChar: 'v', sendIndex: 0, endIndex: 1 }, ]; const charIndexTestOpened = [ @@ -610,23 +687,35 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { { sendChar: 'v', sendIndex: 9, endIndex: 15 }, { sendChar: 'v', sendIndex: 15, endIndex: 15 }, { sendChar: 'i', sendIndex: 15, endIndex: 41 }, - { sendChar: 'o', sendIndex: 41, endIndex: 1 } + { sendChar: 'o', sendIndex: 41, endIndex: 1 }, ]; - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); for (let test of charIndexTestClosed) { - // Send character to treeitem await topLevelFolders[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate item t.true( await checkFocus(t, ex.topLevelFolderSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'false' + ); } // Reload page @@ -638,64 +727,99 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); for (let test of charIndexTestOpened) { - // Send character to treeitem await items[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate treeitem t.true( await checkFocus(t, ex.treeitemSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); } }); -ariaTest('asterisk key opens folders', exampleFile, 'key-asterisk', async (t) => { - - /* Test that "*" ONLY opens all top level nodes and no other folders */ +ariaTest( + 'asterisk key opens folders', + exampleFile, + 'key-asterisk', + async (t) => { + /* Test that "*" ONLY opens all top level nodes and no other folders */ - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - const nextLevelFolders = await t.context.queryElements(t, ex.nextLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); + const nextLevelFolders = await t.context.queryElements( + t, + ex.nextLevelFolderSelector + ); - // Send Key - await topLevelFolders[0].sendKeys('*'); + // Send Key + await topLevelFolders[0].sendKeys('*'); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'true'); - await assertAttributeValues(t, ex.nextLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'true' + ); + await assertAttributeValues( + t, + ex.nextLevelFolderSelector, + 'aria-expanded', + 'false' + ); - /* Test that "*" ONLY opens sibling folders at that level */ + /* Test that "*" ONLY opens sibling folders at that level */ - // Send key - await nextLevelFolders[0].sendKeys('*'); + // Send key + await nextLevelFolders[0].sendKeys('*'); - // The subfolders of first top level folder should all be open + // The subfolders of first top level folder should all be open - const subFoldersOfFirstFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[0]); - for (let el of subFoldersOfFirstFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'true', - 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + const subFoldersOfFirstFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[0] ); - } + for (let el of subFoldersOfFirstFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'true', + 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of second top level folder should all be closed + // The subfolders of second top level folder should all be closed - const subFoldersOfSecondFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[1]); - for (let el of subFoldersOfSecondFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfSecondFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[1] ); - } + for (let el of subFoldersOfSecondFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of third top level folder should all be closed + // The subfolders of third top level folder should all be closed - const subFoldersOfThirdFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[2]); - for (let el of subFoldersOfThirdFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfThirdFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[2] ); + for (let el of subFoldersOfThirdFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } } - -}); +); diff --git a/test/tests/treeview_treeview-2b.js b/test/tests/treeview_treeview-2b.js index a00eec4c48..7d7dd7d9ec 100644 --- a/test/tests/treeview_treeview-2b.js +++ b/test/tests/treeview_treeview-2b.js @@ -19,7 +19,7 @@ const ex = { groupItemSelectors: { 1: [ // Top level folders - '[role="tree"]>[role="treeitem"]' + '[role="tree"]>[role="treeitem"]', ], 2: [ // Content of first top level folder @@ -29,7 +29,7 @@ const ex = { '[role="tree"]>[role="treeitem"]:nth-of-type(2)>[role="group"]>li', // Content of third top level folder - '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>li' + '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>li', ], 3: [ @@ -45,9 +45,9 @@ const ex = { // Content of subfolders of third top level folder '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(1) [role="treeitem"]', '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(2) [role="treeitem"]', - '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(3) [role="treeitem"]' - ] - } + '[role="tree"]>[role="treeitem"]:nth-of-type(3)>[role="group"]>[role="treeitem"]:nth-of-type(3) [role="treeitem"]', + ], + }, }; const openAllFolders = async function (t) { @@ -62,11 +62,15 @@ const openAllFolders = async function (t) { }; const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function (/* selector, index*/) { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); + return t.context.session.executeScript( + function (/* selector, index*/) { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); }; const checkFocusOnParentFolder = async function (t, el) { @@ -75,11 +79,17 @@ const checkFocusOnParentFolder = async function (t, el) { // the element is a folder if (el.hasAttribute('aria-expanded')) { - return document.activeElement === el.parentElement.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.parentElement.closest('[role="treeitem"][aria-expanded]') + ); } // the element is a folder else { - return document.activeElement === el.closest('[role="treeitem"][aria-expanded]'); + return ( + document.activeElement === + el.closest('[role="treeitem"][aria-expanded]') + ); } }, el); }; @@ -95,12 +105,12 @@ const isFolderTreeitem = async function (el) { return (await el.getTagName()) === 'li'; }; -const isOpenedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'true'; +const isOpenedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'true'; }; -const isClosedFolderTreeitem = async function (el) { - return await el.getAttribute('aria-expanded') === 'false'; +const isClosedFolderTreeitem = async function (el) { + return (await el.getAttribute('aria-expanded')) === 'false'; }; const hasAriaExpandedAttribute = async function (t, el) { @@ -111,8 +121,6 @@ const hasAriaExpandedAttribute = async function (t, el) { }; ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { - - const trees = await t.context.queryElements(t, ex.treeSelector); t.is( @@ -128,45 +136,47 @@ ariaTest('role="tree" on ul element', exampleFile, 'tree-role', async (t) => { ); }); -ariaTest('aria-labelledby on role="tree" element', exampleFile, 'tree-aria-labelledby', async (t) => { - - - await assertAriaLabelledby(t, ex.treeSelector); -}); - -ariaTest('role="treeitem" on "li" or "a" element', exampleFile, 'treeitem-role', async (t) => { - - - // Get all the list items in the tree structure - const listitems = await t.context.queryElements(t, '#ex1 [role="tree"] li'); - - // Check the role "treeitem" is on the list item (in the case of a directory) or contained link - for (let item of listitems) { - - const hasAriaExpanded = await hasAriaExpandedAttribute(t, item); - - // if "aria-expanded" is contained on the list item, it is a directory - if (hasAriaExpanded) { - t.is( - await item.getAttribute('role'), - 'treeitem', - 'role="treeitem" should be found on a "li" items that have attribute "aria-expanded"' - ); - } - else { - const links = await t.context.queryElements(t, 'a', item); - t.is( - await links[0].getAttribute('role'), - 'treeitem', - 'role="treeitem" should be found on focusable "a" elements within tree structure' - ); +ariaTest( + 'aria-labelledby on role="tree" element', + exampleFile, + 'tree-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.treeSelector); + } +); + +ariaTest( + 'role="treeitem" on "li" or "a" element', + exampleFile, + 'treeitem-role', + async (t) => { + // Get all the list items in the tree structure + const listitems = await t.context.queryElements(t, '#ex1 [role="tree"] li'); + + // Check the role "treeitem" is on the list item (in the case of a directory) or contained link + for (let item of listitems) { + const hasAriaExpanded = await hasAriaExpandedAttribute(t, item); + + // if "aria-expanded" is contained on the list item, it is a directory + if (hasAriaExpanded) { + t.is( + await item.getAttribute('role'), + 'treeitem', + 'role="treeitem" should be found on a "li" items that have attribute "aria-expanded"' + ); + } else { + const links = await t.context.queryElements(t, 'a', item); + t.is( + await links[0].getAttribute('role'), + 'treeitem', + 'role="treeitem" should be found on focusable "a" elements within tree structure' + ); + } } } -}); +); ariaTest('role="none" on "li" element', exampleFile, 'none-role', async (t) => { - - // Get all the list items in the tree structure const listitems = await t.context.queryElements(t, '#ex1 [role="tree"] li'); @@ -184,331 +194,400 @@ ariaTest('role="none" on "li" element', exampleFile, 'none-role', async (t) => { } }); - -ariaTest('treeitem tabindex set by roving tabindex', exampleFile, 'treeitem-tabindex', async (t) => { +ariaTest( + 'treeitem tabindex set by roving tabindex', + exampleFile, + 'treeitem-tabindex', + async (t) => { await openAllFolders(t); - await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); -}); - -ariaTest('aria-expanded attribute on treeitem matches dom', exampleFile, 'treeitem-aria-expanded', async (t) => { - - - const folders = await t.context.queryElements(t, ex.folderSelector); - - for (let folder of folders) { - - // If the folder is displayed - if (await folder.isDisplayed()) { - - const folderText = await folder.getText(); - - // By default, all folders will be closed - t.is( - await folder.getAttribute('aria-expanded'), - 'false' - ); - t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - false - ); - - // Send enter to the folder - await folder.sendKeys(Key.ENTER); - - // After click, it should be open - t.is( - await folder.getAttribute('aria-expanded'), - 'true' - ); - t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folder)).isDisplayed(), - true - ); - } + await assertRovingTabindex(t, ex.treeitemSelector, Key.ARROW_DOWN); } +); + +ariaTest( + 'aria-expanded attribute on treeitem matches dom', + exampleFile, + 'treeitem-aria-expanded', + async (t) => { + const folders = await t.context.queryElements(t, ex.folderSelector); + + for (let folder of folders) { + // If the folder is displayed + if (await folder.isDisplayed()) { + const folderText = await folder.getText(); + + // By default, all folders will be closed + t.is(await folder.getAttribute('aria-expanded'), 'false'); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + false + ); + + // Send enter to the folder + await folder.sendKeys(Key.ENTER); + + // After click, it should be open + t.is(await folder.getAttribute('aria-expanded'), 'true'); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folder) + ).isDisplayed(), + true + ); + } + } - for (let i = (folders.length - 1); i >= 0; i--) { - - // If the folder is displayed - if (await folders[i].isDisplayed()) { - - const folderText = await folders[i].getText(); - - // Send enter to the folder - await folders[i].sendKeys(Key.ENTER); - - // After sending enter, it should be closed - t.is( - await folders[i].getAttribute('aria-expanded'), - 'false', - folderText - ); - t.is( - await (await t.context.queryElement(t, '[role="treeitem"]', folders[i])).isDisplayed(), - false, - folderText - ); + for (let i = folders.length - 1; i >= 0; i--) { + // If the folder is displayed + if (await folders[i].isDisplayed()) { + const folderText = await folders[i].getText(); + + // Send enter to the folder + await folders[i].sendKeys(Key.ENTER); + + // After sending enter, it should be closed + t.is( + await folders[i].getAttribute('aria-expanded'), + 'false', + folderText + ); + t.is( + await ( + await t.context.queryElement(t, '[role="treeitem"]', folders[i]) + ).isDisplayed(), + false, + folderText + ); + } } } - -}); - - -ariaTest('"aria-setsize" attribute on treeitem', exampleFile, 'treeitem-aria-setsize', async (t) => { - - for (const [level, levelSelectors] of Object.entries(ex.groupItemSelectors)) { - for (const selector of levelSelectors) { - - const items = await t.context.queryElements(t, selector); - const setsize = items.length; - - for (const item of items) { - - // The item is a folder with "treeitem" role and "aria-setsize" set - if (await item.getAttribute('role') === 'treeitem') { - t.is( - await item.getAttribute('aria-setsize'), - setsize.toString(), - '"aria-setsize" attribute should be set to group size (' + setsize + ') in group "' + selector + '"' - ); - } - - // The item is a li that contains a link the "treeitem" role and "aria-setsize" set - else { - let treeitem = item.findElement(By.css('[role="treeitem"]')); - t.is( - await treeitem.getAttribute('aria-setsize'), - setsize.toString(), - '"aria-setsize" attribute should be set to group size (' + setsize + ') in group "' + selector + '"' - ); +); + +ariaTest( + '"aria-setsize" attribute on treeitem', + exampleFile, + 'treeitem-aria-setsize', + async (t) => { + for (const [level, levelSelectors] of Object.entries( + ex.groupItemSelectors + )) { + for (const selector of levelSelectors) { + const items = await t.context.queryElements(t, selector); + const setsize = items.length; + + for (const item of items) { + // The item is a folder with "treeitem" role and "aria-setsize" set + if ((await item.getAttribute('role')) === 'treeitem') { + t.is( + await item.getAttribute('aria-setsize'), + setsize.toString(), + '"aria-setsize" attribute should be set to group size (' + + setsize + + ') in group "' + + selector + + '"' + ); + } + + // The item is a li that contains a link the "treeitem" role and "aria-setsize" set + else { + let treeitem = item.findElement(By.css('[role="treeitem"]')); + t.is( + await treeitem.getAttribute('aria-setsize'), + setsize.toString(), + '"aria-setsize" attribute should be set to group size (' + + setsize + + ') in group "' + + selector + + '"' + ); + } } } } } - -}); - -ariaTest('"aria-posinset" attribute on treeitem', exampleFile, 'treeitem-aria-posinset', async (t) => { - - for (const [level, levelSelectors] of Object.entries(ex.groupItemSelectors)) { - for (const selector of levelSelectors) { - - const items = await t.context.queryElements(t, selector); - let pos = 0; - - for (const item of items) { - pos++; - - // The item is a folder with "treeitem" role and "aria-posinset" set - if (await item.getAttribute('role') === 'treeitem') { - t.is( - await item.getAttribute('aria-posinset'), - pos.toString(), - '"aria-posinset" attribute should be set to "' + pos + '" for treeitem in group "' + selector + '"' - ); - } - - // The item is a li that contains a link the "treeitem" role and "aria-posinset" set - else { - let treeitem = item.findElement(By.css('[role="treeitem"]')); - t.is( - await treeitem.getAttribute('aria-posinset'), - pos.toString(), - '"aria-posinset" attribute should be set to "' + pos + '" for treeitem in group "' + selector + '"' - ); +); + +ariaTest( + '"aria-posinset" attribute on treeitem', + exampleFile, + 'treeitem-aria-posinset', + async (t) => { + for (const [level, levelSelectors] of Object.entries( + ex.groupItemSelectors + )) { + for (const selector of levelSelectors) { + const items = await t.context.queryElements(t, selector); + let pos = 0; + + for (const item of items) { + pos++; + + // The item is a folder with "treeitem" role and "aria-posinset" set + if ((await item.getAttribute('role')) === 'treeitem') { + t.is( + await item.getAttribute('aria-posinset'), + pos.toString(), + '"aria-posinset" attribute should be set to "' + + pos + + '" for treeitem in group "' + + selector + + '"' + ); + } + + // The item is a li that contains a link the "treeitem" role and "aria-posinset" set + else { + let treeitem = item.findElement(By.css('[role="treeitem"]')); + t.is( + await treeitem.getAttribute('aria-posinset'), + pos.toString(), + '"aria-posinset" attribute should be set to "' + + pos + + '" for treeitem in group "' + + selector + + '"' + ); + } } } } } -}); - -ariaTest('"aria-level" attribute on treeitem', exampleFile, 'treeitem-aria-level', async (t) => { - - for (const [level, levelSelectors] of Object.entries(ex.groupItemSelectors)) { - for (const selector of levelSelectors) { - - const items = await t.context.queryElements(t, selector); - for (const item of items) { - - // The item is a folder with "treeitem" role and "aria-level" set - if (await item.getAttribute('role') === 'treeitem') { - - t.is( - await item.getAttribute('aria-level'), - level.toString(), - '"aria-level" attribute should be set to level "' + level + '" in group "' + selector + '"' - ); - } - - // The item is a li that contains a link the "treeitem" role and "aria-level" set - else { - let treeitem = item.findElement(By.css('[role="treeitem"]')); - t.is( - await treeitem.getAttribute('aria-level'), - level.toString(), - '"aria-level" attribute should be set to level "' + level + '" in group "' + selector + '"' - ); +); + +ariaTest( + '"aria-level" attribute on treeitem', + exampleFile, + 'treeitem-aria-level', + async (t) => { + for (const [level, levelSelectors] of Object.entries( + ex.groupItemSelectors + )) { + for (const selector of levelSelectors) { + const items = await t.context.queryElements(t, selector); + for (const item of items) { + // The item is a folder with "treeitem" role and "aria-level" set + if ((await item.getAttribute('role')) === 'treeitem') { + t.is( + await item.getAttribute('aria-level'), + level.toString(), + '"aria-level" attribute should be set to level "' + + level + + '" in group "' + + selector + + '"' + ); + } + + // The item is a li that contains a link the "treeitem" role and "aria-level" set + else { + let treeitem = item.findElement(By.css('[role="treeitem"]')); + t.is( + await treeitem.getAttribute('aria-level'), + level.toString(), + '"aria-level" attribute should be set to level "' + + level + + '" in group "' + + selector + + '"' + ); + } } } } } -}); - - -ariaTest('role="group" on "ul" elements', exampleFile, 'group-role', async (t) => { - - - const groups = await t.context.queryElements(t, ex.groupSelector); - - t.truthy( - groups.length, - 'role="group" elements should be found by selector: ' + ex.groupSelector - ); - - for (let group of groups) { - t.is( - await group.getTagName(), - 'ul', - 'role="group" should be found on a "ul"' +); + +ariaTest( + 'role="group" on "ul" elements', + exampleFile, + 'group-role', + async (t) => { + const groups = await t.context.queryElements(t, ex.groupSelector); + + t.truthy( + groups.length, + 'role="group" elements should be found by selector: ' + ex.groupSelector ); - } -}); -// Keys - -ariaTest('Key enter opens folder and activates link', exampleFile, 'key-enter-or-space', async (t) => { - - let folders = await t.context.queryElements(t, ex.folderSelector); - - // Going through all closed folder elements in dom order will open parent - // folders first, therefore all child folders will be visible before sending "enter" - for (let folder of folders) { - await folder.sendKeys(Key.ENTER); + for (let group of groups) { + t.is( + await group.getTagName(), + 'ul', + 'role="group" should be found on a "ul"' + ); + } } +); - // Assert that the attribute value "aria-expanded" on all folders is "true" - await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); - - // Test a leaf node - let leafnodes = await t.context.queryElements(t, ex.linkSelector); - await leafnodes[0].sendKeys(Key.ENTER); +// Keys - await t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime).catch(() => {}); +ariaTest( + 'Key enter opens folder and activates link', + exampleFile, + 'key-enter-or-space', + async (t) => { + let folders = await t.context.queryElements(t, ex.folderSelector); - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'ENTER key on first element found by selector "' + ex.linkSelector + '" should activate link.' - ); + // Going through all closed folder elements in dom order will open parent + // folders first, therefore all child folders will be visible before sending "enter" + for (let folder of folders) { + await folder.sendKeys(Key.ENTER); + } -}); + // Assert that the attribute value "aria-expanded" on all folders is "true" + await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); + + // Test a leaf node + let leafnodes = await t.context.queryElements(t, ex.linkSelector); + await leafnodes[0].sendKeys(Key.ENTER); + + await t.context.session + .wait(() => { + return t.context.session.getCurrentUrl().then((url) => { + return url != t.context.url; + }); + }, t.context.waitTime) + .catch(() => {}); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'ENTER key on first element found by selector "' + + ex.linkSelector + + '" should activate link.' + ); + } +); // This test fails due to bug #869. -ariaTest.failing('Key space opens folder and activates link', exampleFile, 'key-enter-or-space', async (t) => { - - let folders = await t.context.queryElements(t, ex.folderSelector); +ariaTest.failing( + 'Key space opens folder and activates link', + exampleFile, + 'key-enter-or-space', + async (t) => { + let folders = await t.context.queryElements(t, ex.folderSelector); + + // Going through all closed folder elements in dom order will open parent + // folders first, therefore all child folders will be visible before sending "enter" + for (let folder of folders) { + await folder.sendKeys(Key.SPACE); + } - // Going through all closed folder elements in dom order will open parent - // folders first, therefore all child folders will be visible before sending "enter" - for (let folder of folders) { - await folder.sendKeys(Key.SPACE); + // Assert that the attribute value "aria-expanded" on all folders is "true" + await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); + + // Test a leaf node + let leafnodes = await t.context.queryElements(t, ex.linkSelector); + await leafnodes[0].sendKeys(Key.SPACE); + + await t.context.session + .wait(() => { + return t.context.session.getCurrentUrl().then((url) => { + return url != t.context.url; + }); + }, t.context.waitTime) + .catch(() => {}); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'SPACE key on first element found by selector "' + + ex.linkSelector + + '" should activate link.' + ); } +); + +ariaTest( + 'key down arrow moves focus', + exampleFile, + 'key-down-arrow', + async (t) => { + // Check that the down arrow does not open folders + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - // Assert that the attribute value "aria-expanded" on all folders is "true" - await assertAttributeValues(t, ex.folderSelector, 'aria-expanded', 'true'); - - // Test a leaf node - let leafnodes = await t.context.queryElements(t, ex.linkSelector); - await leafnodes[0].sendKeys(Key.SPACE); - - await t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime).catch(() => {}); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'SPACE key on first element found by selector "' + ex.linkSelector + '" should activate link.' - ); -}); - -ariaTest('key down arrow moves focus', exampleFile, 'key-down-arrow', async (t) => { - - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - - for (let i = 0; i < topLevelFolders.length; i++) { - await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); + for (let i = 0; i < topLevelFolders.length; i++) { + await topLevelFolders[i].sendKeys(Key.ARROW_DOWN); - // If we are on the last top level folder, the focus will not move - const nextIndex = i === topLevelFolders.length - 1 ? - i : - i + 1; + // If we are on the last top level folder, the focus will not move + const nextIndex = i === topLevelFolders.length - 1 ? i : i + 1; - t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex + ); - t.is( - await topLevelFolders[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_DOWN to top level folder at index ' + i + ' should not expand the folder' - ); - } + t.is( + await topLevelFolders[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_DOWN to top level folder at index ' + + i + + ' should not expand the folder' + ); + } - // Reload page - await t.context.session.get(await t.context.session.getCurrentUrl()); + // Reload page + await t.context.session.get(await t.context.session.getCurrentUrl()); - // Open all folders - await openAllFolders(t); + // Open all folders + await openAllFolders(t); - const items = await t.context.queryElements(t, ex.treeitemSelector); + const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = 0; i < items.length; i++) { - await items[i].sendKeys(Key.ARROW_DOWN); + for (let i = 0; i < items.length; i++) { + await items[i].sendKeys(Key.ARROW_DOWN); - // If we are on the last item, the focus will not move - const nextIndex = i === items.length - 1 ? - i : - i + 1; + // If we are on the last item, the focus will not move + const nextIndex = i === items.length - 1 ? i : i + 1; - t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_DOWN to folder/item at index ' + i + ' will move focus to ' + nextIndex - ); + t.true( + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_DOWN to folder/item at index ' + + i + + ' will move focus to ' + + nextIndex + ); + } } -}); +); ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { - // Check that the down arrow does not open folders - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.ARROW_UP); // If we are on the last top level folder, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.topLevelFolderSelector, nextIndex), - 'Sending key ARROW_UP to top level folder at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.topLevelFolderSelector, nextIndex), + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' will move focus to ' + + nextIndex ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key ARROW_UP to top level folder at index ' + i + ' should not expand the folder' + 'Sending key ARROW_UP to top level folder at index ' + + i + + ' should not expand the folder' ); } @@ -520,158 +599,178 @@ ariaTest('key up arrow moves focus', exampleFile, 'key-up-arrow', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.ARROW_UP); // If we are on the last item, the focus will not move - const nextIndex = i === 0 ? - i : - i - 1; + const nextIndex = i === 0 ? i : i - 1; t.true( - await checkFocus(t, ex.treeitemSelector, nextIndex), - 'Sending key ARROW_UP to folder/item at index ' + i + ' will move focus to ' + nextIndex + await checkFocus(t, ex.treeitemSelector, nextIndex), + 'Sending key ARROW_UP to folder/item at index ' + + i + + ' will move focus to ' + + nextIndex ); } }); -ariaTest('key right arrow opens folders and moves focus', exampleFile, 'key-right-arrow', async (t) => { - - const items = await t.context.queryElements(t, ex.treeitemSelector); - - let i = 0; - while (i < items.length) { - - const isFolder = await isFolderTreeitem(items[i]); - const isClosed = await isClosedFolderTreeitem(items[i]); - - await items[i].sendKeys(Key.ARROW_RIGHT); - - // If the item is a folder and it was originally closed - if (isFolder && isClosed) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'true', - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder is closed should open the folder' - ); - - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' when the folder was closed should not move the focus' - ); - continue; - } - - // If the folder is an open folder, the focus will move - else if (isFolder) { - t.true( - await checkFocus(t, ex.treeitemSelector, i + 1), - 'Sending key ARROW_RIGHT to folder at treeitem index ' + i + - ' should move focus to item ' + (i + 1) - ); - } +ariaTest( + 'key right arrow opens folders and moves focus', + exampleFile, + 'key-right-arrow', + async (t) => { + const items = await t.context.queryElements(t, ex.treeitemSelector); + + let i = 0; + while (i < items.length) { + const isFolder = await isFolderTreeitem(items[i]); + const isClosed = await isClosedFolderTreeitem(items[i]); + + await items[i].sendKeys(Key.ARROW_RIGHT); + + // If the item is a folder and it was originally closed + if (isFolder && isClosed) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'true', + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder is closed should open the folder' + ); + + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' when the folder was closed should not move the focus' + ); + continue; + } - // If we are a link, the focus will not move - else { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_RIGHT to link item at treeitem index ' + i + ' should not move focus' - ); + // If the folder is an open folder, the focus will move + else if (isFolder) { + t.true( + await checkFocus(t, ex.treeitemSelector, i + 1), + 'Sending key ARROW_RIGHT to folder at treeitem index ' + + i + + ' should move focus to item ' + + (i + 1) + ); + } + // If we are a link, the focus will not move + else { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_RIGHT to link item at treeitem index ' + + i + + ' should not move focus' + ); + } + i++; } - i++; } -}); +); // This test fails due to bug #866. -ariaTest.failing('key left arrow closes folders and moves focus', exampleFile, 'key-left-arrow', async (t) => { - - // Open all folders - await openAllFolders(t); - - const items = await t.context.queryElements(t, ex.treeitemSelector); - - let i = items.length - 1; - while (i > 0) { - - const isFolder = await isFolderTreeitem(items[i]); - const isOpened = await isOpenedFolderTreeitem(items[i]); - const isTopLevel = isFolder ? - await isTopLevelFolder(t, items[i]) : - false; - - await items[i].sendKeys(Key.ARROW_LEFT); - - // If the item is a folder and the folder was opened, arrow will close folder - if (isFolder && isOpened) { - t.is( - await items[i].getAttribute('aria-expanded'), - 'false', - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should close the folder' - ); +ariaTest.failing( + 'key left arrow closes folders and moves focus', + exampleFile, + 'key-left-arrow', + async (t) => { + // Open all folders + await openAllFolders(t); - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to folder at treeitem index ' + i + - ' when the folder is opened should not move the focus' - ); - // Send one more arrow key to the folder that is now closed - continue; - } + const items = await t.context.queryElements(t, ex.treeitemSelector); + + let i = items.length - 1; + while (i > 0) { + const isFolder = await isFolderTreeitem(items[i]); + const isOpened = await isOpenedFolderTreeitem(items[i]); + const isTopLevel = isFolder ? await isTopLevelFolder(t, items[i]) : false; + + await items[i].sendKeys(Key.ARROW_LEFT); + + // If the item is a folder and the folder was opened, arrow will close folder + if (isFolder && isOpened) { + t.is( + await items[i].getAttribute('aria-expanded'), + 'false', + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should close the folder' + ); + + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to folder at treeitem index ' + + i + + ' when the folder is opened should not move the focus' + ); + // Send one more arrow key to the folder that is now closed + continue; + } - // If the item is a top level folder and closed, arrow will do nothing - else if (isTopLevel) { - t.true( - await checkFocus(t, ex.treeitemSelector, i), - 'Sending key ARROW_LEFT to link in top level folder at treeitem index ' + i + - ' should not move focus' - ); - } + // If the item is a top level folder and closed, arrow will do nothing + else if (isTopLevel) { + t.true( + await checkFocus(t, ex.treeitemSelector, i), + 'Sending key ARROW_LEFT to link in top level folder at treeitem index ' + + i + + ' should not move focus' + ); + } - // If the item is a link in folder, or a closed folder, arrow will move up a folder - else { - t.true( - await checkFocusOnParentFolder(t, items[i]), - 'Sending key ARROW_LEFT to link in folder at treeitem index ' + i + - ' should move focus to parent folder' - ); + // If the item is a link in folder, or a closed folder, arrow will move up a folder + else { + t.true( + await checkFocusOnParentFolder(t, items[i]), + 'Sending key ARROW_LEFT to link in folder at treeitem index ' + + i + + ' should move focus to parent folder' + ); + + t.is( + await items[i].isDisplayed(), + true, + 'Sending key ARROW_LEFT to link in folder at treeitem index ' + + i + + ' should not close the folder it is in' + ); + } - t.is( - await items[i].isDisplayed(), - true, - 'Sending key ARROW_LEFT to link in folder at treeitem index ' + i + - ' should not close the folder it is in' - ); + i--; } - - i--; } -}); +); ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { - // Test that key "home" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.topLevelFolderSelector, 0), - 'Sending key HOME to top level folder at index ' + i + ' should move focus to first top level folder' + await checkFocus(t, ex.topLevelFolderSelector, 0), + 'Sending key HOME to top level folder at index ' + + i + + ' should move focus to first top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key HOME to top level folder at index ' + i + ' should not expand the folder' + 'Sending key HOME to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(await t.context.session.getCurrentUrl()); @@ -680,37 +779,48 @@ ariaTest('key home moves focus', exampleFile, 'key-home', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.HOME); t.true( - await checkFocus(t, ex.treeitemSelector, 0), - 'Sending key HOME to top level folder/item at index ' + i + ' will move focus to the first item' + await checkFocus(t, ex.treeitemSelector, 0), + 'Sending key HOME to top level folder/item at index ' + + i + + ' will move focus to the first item' ); } }); ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { - // Test that key "end" works when no folder is open - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); - for (let i = topLevelFolders.length - 1; i >= 0 ; i--) { + for (let i = topLevelFolders.length - 1; i >= 0; i--) { await topLevelFolders[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.topLevelFolderSelector, topLevelFolders.length - 1), - 'Sending key END to top level folder at index ' + i + ' should move focus to last top level folder' + await checkFocus( + t, + ex.topLevelFolderSelector, + topLevelFolders.length - 1 + ), + 'Sending key END to top level folder at index ' + + i + + ' should move focus to last top level folder' ); t.is( await topLevelFolders[i].getAttribute('aria-expanded'), 'false', - 'Sending key END to top level folder at index ' + i + ' should not expand the folder' + 'Sending key END to top level folder at index ' + + i + + ' should not expand the folder' ); } - // Reload page await t.context.session.get(await t.context.session.getCurrentUrl()); @@ -719,23 +829,23 @@ ariaTest('key end moves focus', exampleFile, 'key-end', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); - for (let i = items.length - 1; i >= 0 ; i--) { + for (let i = items.length - 1; i >= 0; i--) { await items[i].sendKeys(Key.END); t.true( - await checkFocus(t, ex.treeitemSelector, (items.length - 1)), - 'Sending key END to top level folder/item at index ' + i + + await checkFocus(t, ex.treeitemSelector, items.length - 1), + 'Sending key END to top level folder/item at index ' + + i + ' will move focus to the last item in the last opened folder' ); } }); ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { - const charIndexTestClosed = [ { sendChar: 'g', sendIndex: 0, endIndex: 2 }, { sendChar: 'f', sendIndex: 2, endIndex: 0 }, - { sendChar: 'v', sendIndex: 0, endIndex: 1 } + { sendChar: 'v', sendIndex: 0, endIndex: 1 }, ]; const charIndexTestOpened = [ @@ -744,23 +854,35 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { { sendChar: 'v', sendIndex: 9, endIndex: 15 }, { sendChar: 'v', sendIndex: 15, endIndex: 15 }, { sendChar: 'i', sendIndex: 15, endIndex: 41 }, - { sendChar: 'o', sendIndex: 41, endIndex: 1 } + { sendChar: 'o', sendIndex: 41, endIndex: 1 }, ]; - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); for (let test of charIndexTestClosed) { - // Send character to treeitem await topLevelFolders[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate item t.true( await checkFocus(t, ex.topLevelFolderSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'false' + ); } // Reload page @@ -772,64 +894,99 @@ ariaTest('characters move focus', exampleFile, 'key-character', async (t) => { const items = await t.context.queryElements(t, ex.treeitemSelector); for (let test of charIndexTestOpened) { - // Send character to treeitem await items[test.sendIndex].sendKeys(test.sendChar); // Test that the focus switches to the appropriate treeitem t.true( await checkFocus(t, ex.treeitemSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to treeitem ' + test.sendIndex + ' should move the foucs to treeitem ' + test.endIndex + 'Sending character ' + + test.sendChar + + ' to treeitem ' + + test.sendIndex + + ' should move the foucs to treeitem ' + + test.endIndex ); } }); -ariaTest('asterisk key opens folders', exampleFile, 'key-asterisk', async (t) => { - - /* Test that "*" ONLY opens all top level nodes and no other folders */ +ariaTest( + 'asterisk key opens folders', + exampleFile, + 'key-asterisk', + async (t) => { + /* Test that "*" ONLY opens all top level nodes and no other folders */ - const topLevelFolders = await t.context.queryElements(t, ex.topLevelFolderSelector); - const nextLevelFolders = await t.context.queryElements(t, ex.nextLevelFolderSelector); + const topLevelFolders = await t.context.queryElements( + t, + ex.topLevelFolderSelector + ); + const nextLevelFolders = await t.context.queryElements( + t, + ex.nextLevelFolderSelector + ); - // Send Key - await topLevelFolders[0].sendKeys('*'); + // Send Key + await topLevelFolders[0].sendKeys('*'); - await assertAttributeValues(t, ex.topLevelFolderSelector, 'aria-expanded', 'true'); - await assertAttributeValues(t, ex.nextLevelFolderSelector, 'aria-expanded', 'false'); + await assertAttributeValues( + t, + ex.topLevelFolderSelector, + 'aria-expanded', + 'true' + ); + await assertAttributeValues( + t, + ex.nextLevelFolderSelector, + 'aria-expanded', + 'false' + ); - /* Test that "*" ONLY opens sibling folders at that level */ + /* Test that "*" ONLY opens sibling folders at that level */ - // Send key - await nextLevelFolders[0].sendKeys('*'); + // Send key + await nextLevelFolders[0].sendKeys('*'); - // The subfolders of first top level folder should all be open + // The subfolders of first top level folder should all be open - const subFoldersOfFirstFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[0]); - for (let el of subFoldersOfFirstFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'true', - 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + const subFoldersOfFirstFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[0] ); - } + for (let el of subFoldersOfFirstFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'true', + 'Subfolders under the first top level folder should all be opened after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of second top level folder should all be closed + // The subfolders of second top level folder should all be closed - const subFoldersOfSecondFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[1]); - for (let el of subFoldersOfSecondFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfSecondFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[1] ); - } + for (let el of subFoldersOfSecondFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the second top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } - // The subfolders of third top level folder should all be closed + // The subfolders of third top level folder should all be closed - const subFoldersOfThirdFolder = await t.context.queryElements(t, ex.nextLevelFolderSelector, topLevelFolders[2]); - for (let el of subFoldersOfThirdFolder) { - t.true( - await el.getAttribute('aria-expanded') === 'false', - 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + const subFoldersOfThirdFolder = await t.context.queryElements( + t, + ex.nextLevelFolderSelector, + topLevelFolders[2] ); + for (let el of subFoldersOfThirdFolder) { + t.true( + (await el.getAttribute('aria-expanded')) === 'false', + 'Subfolders under the third top level folder should all be closed after sending one "*" to subfolder under first top level folder' + ); + } } - -}); +); diff --git a/test/util/assertAriaActivedescendant.js b/test/util/assertAriaActivedescendant.js index 74ddabd6cf..074b50b604 100644 --- a/test/util/assertAriaActivedescendant.js +++ b/test/util/assertAriaActivedescendant.js @@ -12,8 +12,12 @@ const assert = require('assert'); * @param {String} optionsSelector - selector to select list of candidate elements for focus * @param {Number} index - index of element in list returned by optionsSelector with focus */ -module.exports = async function assertAriaSelectedAndActivedescendant (t, activedescendantSelector, optionsSelector, index) { - +module.exports = async function assertAriaSelectedAndActivedescendant( + t, + activedescendantSelector, + optionsSelector, + index +) { // Confirm aria-activedescendant refers to the correct option const options = await t.context.queryElements(t, optionsSelector); @@ -24,7 +28,10 @@ module.exports = async function assertAriaSelectedAndActivedescendant (t, active .findElement(By.css(activedescendantSelector)) .getAttribute('aria-activedescendant'), optionId, - 'aria-activedescendant should be set to ' + optionId + ' for item: ' + activedescendantSelector + 'aria-activedescendant should be set to ' + + optionId + + ' for item: ' + + activedescendantSelector ); // Confirm the focus is on the aria-activedescendent element @@ -37,7 +44,8 @@ module.exports = async function assertAriaSelectedAndActivedescendant (t, active assert( focused, - 'document focus should be on aria-activedescendant element: ' + activedescendantSelector + 'document focus should be on aria-activedescendant element: ' + + activedescendantSelector ); t.pass(); diff --git a/test/util/assertAriaControls.js b/test/util/assertAriaControls.js index 899d128109..bba00de566 100644 --- a/test/util/assertAriaControls.js +++ b/test/util/assertAriaControls.js @@ -9,15 +9,18 @@ const assert = require('assert'); * @param {String} elementSelector - the element with aria-controls set */ -module.exports = async function assertAriaControls (t, elementSelector) { +module.exports = async function assertAriaControls(t, elementSelector) { const elements = await t.context.queryElements(t, elementSelector); for (let element of elements) { - const ariaControlsExists = await t.context.session.executeScript(async function () { - const selector = arguments[0]; - let el = document.querySelector(selector); - return el.hasAttribute('aria-controls'); - }, elementSelector); + const ariaControlsExists = await t.context.session.executeScript( + async function () { + const selector = arguments[0]; + let el = document.querySelector(selector); + return el.hasAttribute('aria-controls'); + }, + elementSelector + ); assert.ok( ariaControlsExists, @@ -28,7 +31,8 @@ module.exports = async function assertAriaControls (t, elementSelector) { assert.ok( controlId, - '"aria-controls" attribute should have a value on element(s): ' + elementSelector + '"aria-controls" attribute should have a value on element(s): ' + + elementSelector ); const controlEl = await t.context.queryElements(t, `#${controlId}`); @@ -36,7 +40,10 @@ module.exports = async function assertAriaControls (t, elementSelector) { assert.equal( controlEl.length, 1, - 'Element with id "' + controlId + '" should exist as reference by "aria-controls" on: ' + elementSelector + 'Element with id "' + + controlId + + '" should exist as reference by "aria-controls" on: ' + + elementSelector ); } t.pass(); diff --git a/test/util/assertAriaDescribedby.js b/test/util/assertAriaDescribedby.js index f679c2c287..7e99a12e02 100644 --- a/test/util/assertAriaDescribedby.js +++ b/test/util/assertAriaDescribedby.js @@ -10,44 +10,58 @@ const assert = require('assert'); * @param {String} elementSelector - the element with aria-describedby set */ -module.exports = async function assertAriaDescribedby (t, elementSelector) { +module.exports = async function assertAriaDescribedby(t, elementSelector) { const elements = await t.context.queryElements(t, elementSelector); for (let index = 0; index < elements.length; index++) { - - let ariaDescribedbyExists = await t.context.session.executeScript(async function () { - const [selector, index] = arguments; - let els = document.querySelectorAll(selector); - return els[index].hasAttribute('aria-describedby'); - }, elementSelector, index); + let ariaDescribedbyExists = await t.context.session.executeScript( + async function () { + const [selector, index] = arguments; + let els = document.querySelectorAll(selector); + return els[index].hasAttribute('aria-describedby'); + }, + elementSelector, + index + ); assert( ariaDescribedbyExists, '"aria-describedby" attribute should exist on element: ' + elementSelector ); - let descriptionValue = await elements[index].getAttribute('aria-describedby'); + let descriptionValue = await elements[index].getAttribute( + 'aria-describedby' + ); assert.ok( descriptionValue, - '"aria-describedby" attribute should have a value on element: ' + elementSelector + '"aria-describedby" attribute should have a value on element: ' + + elementSelector ); const descriptionIds = descriptionValue.split(' '); for (let descriptionId of descriptionIds) { + let descriptionText = await t.context.session.executeScript( + async function () { + const id = arguments[0]; + let el = document.querySelector('#' + id); + return el.innerText; + }, + descriptionId + ); - let descriptionText = await t.context.session.executeScript(async function () { - const id = arguments[0]; - let el = document.querySelector('#' + id); - return el.innerText; - }, descriptionId); - - let descriptionImage = await t.context.queryElements(t, `#${descriptionId}`); + let descriptionImage = await t.context.queryElements( + t, + `#${descriptionId}` + ); assert.ok( descriptionText || descriptionImage.length, - 'Element with id "' + descriptionId + '" should contain description text (or image) according to attribute "aria-describedby" on element: ' + elementSelector + 'Element with id "' + + descriptionId + + '" should contain description text (or image) according to attribute "aria-describedby" on element: ' + + elementSelector ); } } diff --git a/test/util/assertAriaLabelExists.js b/test/util/assertAriaLabelExists.js index fd2519d90e..1baf6460bb 100644 --- a/test/util/assertAriaLabelExists.js +++ b/test/util/assertAriaLabelExists.js @@ -9,16 +9,19 @@ const assert = require('assert'); * @param {String} elementSelector - the element with aria-labelledby set */ -module.exports = async function assertAriaLabel (t, elementSelector) { +module.exports = async function assertAriaLabel(t, elementSelector) { const elements = await t.context.queryElements(t, elementSelector); for (let index = 0; index < elements.length; index++) { - - let ariaLabelExists = await t.context.session.executeScript(async function () { - const [selector, index] = arguments; - let els = document.querySelectorAll(selector); - return els[index].hasAttribute('aria-label'); - }, elementSelector, index); + let ariaLabelExists = await t.context.session.executeScript( + async function () { + const [selector, index] = arguments; + let els = document.querySelectorAll(selector); + return els[index].hasAttribute('aria-label'); + }, + elementSelector, + index + ); assert( ariaLabelExists, @@ -29,7 +32,8 @@ module.exports = async function assertAriaLabel (t, elementSelector) { assert.ok( labelValue, - '"aria-label" attribute should have a value on element(s): ' + elementSelector + '"aria-label" attribute should have a value on element(s): ' + + elementSelector ); } diff --git a/test/util/assertAriaLabelledby.js b/test/util/assertAriaLabelledby.js index 1d4bf2dfc3..9564a5ad77 100644 --- a/test/util/assertAriaLabelledby.js +++ b/test/util/assertAriaLabelledby.js @@ -9,26 +9,32 @@ const assert = require('assert'); * @param {String} elementSelector - the element with aria-labelledby set */ -module.exports = async function assertAriaLabelledby (t, elementSelector) { +module.exports = async function assertAriaLabelledby(t, elementSelector) { const elements = await t.context.queryElements(t, elementSelector); for (let index = 0; index < elements.length; index++) { - const ariaLabelledbyExists = await t.context.session.executeScript(async function () { - const [selector, index] = arguments; - let els = document.querySelectorAll(selector); - return els[index].hasAttribute('aria-labelledby'); - }, elementSelector, index); + const ariaLabelledbyExists = await t.context.session.executeScript( + async function () { + const [selector, index] = arguments; + let els = document.querySelectorAll(selector); + return els[index].hasAttribute('aria-labelledby'); + }, + elementSelector, + index + ); assert( ariaLabelledbyExists, - '"aria-labelledby" attribute should exist on element(s): ' + elementSelector + '"aria-labelledby" attribute should exist on element(s): ' + + elementSelector ); const labelValue = await elements[index].getAttribute('aria-labelledby'); assert.ok( labelValue, - '"aria-labelledby" attribute should have a value on element(s): ' + elementSelector + '"aria-labelledby" attribute should have a value on element(s): ' + + elementSelector ); const labelIds = labelValue.split(' '); @@ -42,7 +48,10 @@ module.exports = async function assertAriaLabelledby (t, elementSelector) { assert.ok( labelText, - 'Element with id "' + labelId + '" should contain label text to describe element: ' + elementSelector + 'Element with id "' + + labelId + + '" should contain label text to describe element: ' + + elementSelector ); } } diff --git a/test/util/assertAriaRoles.js b/test/util/assertAriaRoles.js index bae3c7dfa1..d69a25208f 100644 --- a/test/util/assertAriaRoles.js +++ b/test/util/assertAriaRoles.js @@ -13,7 +13,13 @@ const assert = require('assert'); * @param {String} elementTag - the element the role should be found on */ -module.exports = async function assertAriaRoles (t, exampleId, role, roleCount, elementTag) { +module.exports = async function assertAriaRoles( + t, + exampleId, + role, + roleCount, + elementTag +) { const elementSelector = '#' + exampleId + ' [role="' + role + '"]'; const elements = await t.context.queryElements(t, elementSelector); @@ -21,7 +27,11 @@ module.exports = async function assertAriaRoles (t, exampleId, role, roleCount, assert.equal( elements.length, roleCount, - roleCount + ' role="' + role + '" elements should be found by selector "' + elementSelector + + roleCount + + ' role="' + + role + + '" elements should be found by selector "' + + elementSelector + '" in this example' ); @@ -29,7 +39,11 @@ module.exports = async function assertAriaRoles (t, exampleId, role, roleCount, assert.equal( await element.getTagName(), elementTag, - 'role="' + role + '" should be found on "' + elementTag + '" elements in this example' + 'role="' + + role + + '" should be found on "' + + elementTag + + '" elements in this example' ); } diff --git a/test/util/assertAriaSelectedAndActivedescendant.js b/test/util/assertAriaSelectedAndActivedescendant.js index cb14f20623..e5deb0c56b 100644 --- a/test/util/assertAriaSelectedAndActivedescendant.js +++ b/test/util/assertAriaSelectedAndActivedescendant.js @@ -12,8 +12,12 @@ const assert = require('assert'); * @param {String} optionsSelector - selector to select list of candidate elements for focus * @param {Number} index - index of element in list returned by optionsSelector with focus */ -module.exports = async function assertAriaSelectedAndActivedescendant (t, activedescendantSelector, optionsSelector, index) { - +module.exports = async function assertAriaSelectedAndActivedescendant( + t, + activedescendantSelector, + optionsSelector, + index +) { // Confirm the option at the index has aria-selected set to true const options = await t.context.queryElements(t, optionsSelector); @@ -21,7 +25,10 @@ module.exports = async function assertAriaSelectedAndActivedescendant (t, active assert.strictEqual( await options[index].getAttribute('aria-selected'), 'true', - 'aria-selected should be on item at index ' + index + ' for items: ' + optionsSelector + 'aria-selected should be on item at index ' + + index + + ' for items: ' + + optionsSelector ); // Confirm aria-activedescendant refers to the correct option @@ -33,7 +40,10 @@ module.exports = async function assertAriaSelectedAndActivedescendant (t, active .findElement(By.css(activedescendantSelector)) .getAttribute('aria-activedescendant'), optionId, - 'aria-activedescendant should be set to ' + optionId + ' for items: ' + activedescendantSelector + 'aria-activedescendant should be set to ' + + optionId + + ' for items: ' + + activedescendantSelector ); // Confirm the focus is on the aria-activedescendent element @@ -46,7 +56,8 @@ module.exports = async function assertAriaSelectedAndActivedescendant (t, active assert( focused, - 'document focus should be on aria-activedescendant element: ' + activedescendantSelector + 'document focus should be on aria-activedescendant element: ' + + activedescendantSelector ); t.pass(); diff --git a/test/util/assertAttributeDNE.js b/test/util/assertAttributeDNE.js index 741ad12b87..28c04d5ddb 100644 --- a/test/util/assertAttributeDNE.js +++ b/test/util/assertAttributeDNE.js @@ -9,20 +9,30 @@ const assert = require('assert'); * @param {String} selector - elements to test * @param {String} attribute - attribute that should not exist */ -module.exports = async function assertAttributeDNE (t, selector, attribute) { +module.exports = async function assertAttributeDNE(t, selector, attribute) { const numElements = (await t.context.queryElements(t, selector)).length; for (let index = 0; index < numElements; index++) { - const attributeExists = await t.context.session.executeScript(function () { - let [selector, index, attribute] = arguments; - let elements = document.querySelectorAll(selector); - return elements[index].hasAttribute(attribute); - }, selector, index, attribute); + const attributeExists = await t.context.session.executeScript( + function () { + let [selector, index, attribute] = arguments; + let elements = document.querySelectorAll(selector); + return elements[index].hasAttribute(attribute); + }, + selector, + index, + attribute + ); assert( !attributeExists, - 'Attribute "' + attribute + '" should not exist on element at index ' + index + - ' of elements found by selector "' + selector + '"' + 'Attribute "' + + attribute + + '" should not exist on element at index ' + + index + + ' of elements found by selector "' + + selector + + '"' ); } diff --git a/test/util/assertAttributeValues.js b/test/util/assertAttributeValues.js index 05d7662ca6..5bf3572c69 100644 --- a/test/util/assertAttributeValues.js +++ b/test/util/assertAttributeValues.js @@ -10,14 +10,25 @@ const assert = require('assert'); * @param {String} attribute - the attribute * @param {String} value - the value */ -module.exports = async function assertAttributeValues (t, elementSelector, attribute, value) { +module.exports = async function assertAttributeValues( + t, + elementSelector, + attribute, + value +) { let elements = await t.context.queryElements(t, elementSelector); for (let element of elements) { assert.strictEqual( await element.getAttribute(attribute), value, - 'Attribute "' + attribute + '" with value "' + value + '" should be found on element(s) with selector "' + elementSelector + '"' + 'Attribute "' + + attribute + + '" with value "' + + value + + '" should be found on element(s) with selector "' + + elementSelector + + '"' ); } t.pass(); diff --git a/test/util/assertHasFocus.js b/test/util/assertHasFocus.js index 91909d27dd..55472a246f 100644 --- a/test/util/assertHasFocus.js +++ b/test/util/assertHasFocus.js @@ -1,27 +1,27 @@ -'use strict'; - -const { By, Key } = require('selenium-webdriver'); -const assert = require('assert'); - -/** - * Confirm the continuous subset of elements are in tab order for a test page - * - * @param {obj} t - ava execution object - * @param {string} elementSelector - element selector string - * @param {string} message - optional assertion message - */ -module.exports = async function assertHasFocus (t, selector, message) { - const hasFocus = t.context.session.wait( - async function () { - return t.context.session.executeScript(function () { - selector = arguments[0]; - return document.activeElement === document.querySelector(selector); - }, selector); - }, - t.context.waitTime, - 'Timeout waiting for focus to land on element: ' + selector - ); - - message = message || `Element ${selector} should have focus`; - t.true(await hasFocus, message); +'use strict'; + +const { By, Key } = require('selenium-webdriver'); +const assert = require('assert'); + +/** + * Confirm the continuous subset of elements are in tab order for a test page + * + * @param {obj} t - ava execution object + * @param {string} elementSelector - element selector string + * @param {string} message - optional assertion message + */ +module.exports = async function assertHasFocus(t, selector, message) { + const hasFocus = t.context.session.wait( + async function () { + return t.context.session.executeScript(function () { + selector = arguments[0]; + return document.activeElement === document.querySelector(selector); + }, selector); + }, + t.context.waitTime, + 'Timeout waiting for focus to land on element: ' + selector + ); + + message = message || `Element ${selector} should have focus`; + t.true(await hasFocus, message); }; diff --git a/test/util/assertNoElements.js b/test/util/assertNoElements.js index 94385fdb1e..c6968e597d 100644 --- a/test/util/assertNoElements.js +++ b/test/util/assertNoElements.js @@ -14,9 +14,9 @@ const { By } = require('selenium-webdriver'); * @returns {Promise} Resolves to array of elements */ module.exports = async function assertNoElements(t, selector, message) { - const elements = await t.context.session.findElements(By.css(selector)); - const errorMessage = message || 'Should return no results for CSS selector ' + selector; + const errorMessage = + message || 'Should return no results for CSS selector ' + selector; t.is(elements.length, 0, errorMessage); -} +}; diff --git a/test/util/assertRovingTabindex.js b/test/util/assertRovingTabindex.js index 20cf4ab42b..f8acde8eb3 100644 --- a/test/util/assertRovingTabindex.js +++ b/test/util/assertRovingTabindex.js @@ -10,22 +10,28 @@ const assert = require('assert'); by default, focus should be on the first item * @param {webdriver.Key} key - which key to change roving focus between items */ -module.exports = async function assertRovingTabindex (t, elementsSelector, key) { - +module.exports = async function assertRovingTabindex(t, elementsSelector, key) { // tabindex='0' is expected on the first element let elements = await t.context.queryElements(t, elementsSelector); // test only one element has tabindex="0" for (let tabableEl = 0; tabableEl < elements.length; tabableEl++) { for (let el = 0; el < elements.length; el++) { - let tabindex = el === tabableEl ? '0' : '-1'; assert.equal( await elements[el].getAttribute('tabindex'), tabindex, - 'focus is on element ' + tabableEl + ' of ' + elements.length + ' elements "' + elementsSelector + - '", therefore tabindex on element ' + el + ' should be "' + tabindex + 'focus is on element ' + + tabableEl + + ' of ' + + elements.length + + ' elements "' + + elementsSelector + + '", therefore tabindex on element ' + + el + + ' should be "' + + tabindex ); } @@ -35,4 +41,3 @@ module.exports = async function assertRovingTabindex (t, elementsSelector, key) t.pass(); }; - diff --git a/test/util/assertTabOrder.js b/test/util/assertTabOrder.js index be170fabbd..eadd1f96ed 100644 --- a/test/util/assertTabOrder.js +++ b/test/util/assertTabOrder.js @@ -18,8 +18,7 @@ const focusMatchesElement = async function (t, selector) { * @param {obj} t - ava execution object * @param {Array} tabOrderSelectors - elements in tab order */ -module.exports = async function assertTabOrder (t, tabOrderSelectors) { - +module.exports = async function assertTabOrder(t, tabOrderSelectors) { // Focus on the first element in the list await t.context.session.executeScript(function () { selector = arguments[0]; @@ -37,4 +36,3 @@ module.exports = async function assertTabOrder (t, tabOrderSelectors) { t.pass(); }; - diff --git a/test/util/force-serial.js b/test/util/force-serial.js index 7f3c4128da..892f78d5f2 100644 --- a/test/util/force-serial.js +++ b/test/util/force-serial.js @@ -1,11 +1,11 @@ 'use strict'; const net = require('net'); -function bindPort (port) { +function bindPort(port) { const server = net.createServer(); const release = () => { return new Promise((resolve, reject) => { - server.close((err) => err ? reject(err) : resolve()); + server.close((err) => (err ? reject(err) : resolve())); }); }; @@ -14,8 +14,7 @@ function bindPort (port) { server.on('error', (err) => { if (err.code === 'EADDRINUSE') { resolve(false); - } - else { + } else { reject(err); } }); @@ -32,17 +31,15 @@ function bindPort (port) { * @returns {Promise} eventual value which shares the resolution of the * provided operation */ -module.exports = function forceSerial (port, safe) { - return bindPort(port) - .then((release) => { - if (!release) { - return new Promise((resolve) => setTimeout(resolve, 300)) - .then(() => forceSerial(port, safe)); - } - const operation = new Promise((resolve) => resolve(safe())); +module.exports = function forceSerial(port, safe) { + return bindPort(port).then((release) => { + if (!release) { + return new Promise((resolve) => setTimeout(resolve, 300)).then(() => + forceSerial(port, safe) + ); + } + const operation = new Promise((resolve) => resolve(safe())); - return operation - .then(release, release) - .then(() => operation); - }); + return operation.then(release, release).then(() => operation); + }); }; diff --git a/test/util/get-json.js b/test/util/get-json.js index 4bc3b70634..4f2b227f19 100644 --- a/test/util/get-json.js +++ b/test/util/get-json.js @@ -2,32 +2,36 @@ const http = require('http'); -module.exports = function getJSON (url) { +module.exports = function getJSON(url) { return new Promise((resolve, reject) => { - http.get(url, resolve) - .on('error', reject); - }).then((response) => { - const { statusCode } = response; - const contentType = response.headers['content-type']; + http.get(url, resolve).on('error', reject); + }) + .then((response) => { + const { statusCode } = response; + const contentType = response.headers['content-type']; - if (statusCode !== 200) { - // consume response data to free up memory - response.resume(); - throw new Error(`Request Failed.\nStatus Code: ${statusCode}`); - } - else if (!/^application\/json/.test(contentType)) { - // consume response data to free up memory - response.resume(); - throw new Error('Invalid content-type.\n' + - `Expected application/json but received ${contentType}`); - } + if (statusCode !== 200) { + // consume response data to free up memory + response.resume(); + throw new Error(`Request Failed.\nStatus Code: ${statusCode}`); + } else if (!/^application\/json/.test(contentType)) { + // consume response data to free up memory + response.resume(); + throw new Error( + 'Invalid content-type.\n' + + `Expected application/json but received ${contentType}` + ); + } - response.setEncoding('utf8'); - let rawData = ''; - response.on('data', (chunk) => { rawData += chunk; }); + response.setEncoding('utf8'); + let rawData = ''; + response.on('data', (chunk) => { + rawData += chunk; + }); - return new Promise((resolve) => { - response.on('end', () => resolve(rawData)); - }); - }).then((rawData) => JSON.parse(rawData)); + return new Promise((resolve) => { + response.on('end', () => resolve(rawData)); + }); + }) + .then((rawData) => JSON.parse(rawData)); }; diff --git a/test/util/queryElement.js b/test/util/queryElement.js index cdf8cd5542..d529fa6310 100644 --- a/test/util/queryElement.js +++ b/test/util/queryElement.js @@ -18,4 +18,4 @@ module.exports = async function queryElement(t, selector, context) { t.fail(`Element query returned no result: ${selector}`); } return result; -} +}; diff --git a/test/util/queryElements.js b/test/util/queryElements.js index 23558e17bc..b82dc10503 100644 --- a/test/util/queryElements.js +++ b/test/util/queryElements.js @@ -20,4 +20,4 @@ module.exports = async function queryElements(t, selector, context) { t.fail(`Element query returned no results: ${selector}`); } return result; -} +}; diff --git a/test/util/report.js b/test/util/report.js index 9971f96f64..2780f1647a 100644 --- a/test/util/report.js +++ b/test/util/report.js @@ -9,20 +9,30 @@ const { spawnSync } = require('child_process'); const examplePath = path.resolve(__dirname, '..', '..', 'examples'); const testsPath = path.resolve(__dirname, '..', 'tests'); -const ignoreExampleDirs = path.resolve(__dirname, 'report_files', 'ignore_test_directories'); -const ignoreExampleFiles = path.resolve(__dirname, 'report_files', 'ignore_html_files'); +const ignoreExampleDirs = path.resolve( + __dirname, + 'report_files', + 'ignore_test_directories' +); +const ignoreExampleFiles = path.resolve( + __dirname, + 'report_files', + 'ignore_html_files' +); const ignoredDataTestId = 'test-not-required'; -const ignoreDirectories = fs.readFileSync(ignoreExampleDirs) +const ignoreDirectories = fs + .readFileSync(ignoreExampleDirs) .toString() .trim() .split('\n') - .map(d => path.resolve(examplePath, d)); -const ignoreFiles = fs.readFileSync(ignoreExampleFiles) + .map((d) => path.resolve(examplePath, d)); +const ignoreFiles = fs + .readFileSync(ignoreExampleFiles) .toString() .trim() .split('\n') - .map(f => path.resolve(examplePath, f)); + .map((f) => path.resolve(examplePath, f)); /** * Recursively find all example pages, saves to exampleFiles global @@ -40,8 +50,7 @@ const getExampleFiles = function (currentDirPath, exampleFiles) { ignoreFiles.indexOf(filePath) === -1 ) { exampleFiles.push(filePath); - } - else if ( + } else if ( stat.isDirectory() && ignoreDirectories.indexOf(filePath) === -1 ) { @@ -76,7 +85,6 @@ const getAttributeRowName = function ($, $tableRow) { return rowName; }; - /** * Processes all example files to find data-test-ids and missing data-test-ids * Builds exampleCoverage object: @@ -92,7 +100,10 @@ const getAttributeRowName = function ($, $tableRow) { * @param {Array} exampleFiles - all example files to process * @param {Object} exampleCoverage - object to add coverage information to */ -const processDocumentationInExampleFiles = function (exampleFiles, exampleCoverage) { +const processDocumentationInExampleFiles = function ( + exampleFiles, + exampleCoverage +) { for (let exampleFile of exampleFiles) { var data = fs.readFileSync(exampleFile); const dom = htmlparser2.parseDOM(data); @@ -107,12 +118,13 @@ const processDocumentationInExampleFiles = function (exampleFiles, exampleCovera let $row = $(this); let dataTestId = $row.attr('data-test-id'); - if (dataTestId === ignoredDataTestId) { return; } + if (dataTestId === ignoredDataTestId) { + return; + } if (dataTestId !== undefined) { dataTestIds.add(dataTestId); - } - else { + } else { keysMissingIds.add(getKeyboardRowName($, $row)); } }); @@ -122,12 +134,13 @@ const processDocumentationInExampleFiles = function (exampleFiles, exampleCovera let $row = $(this); let dataTestId = $row.attr('data-test-id'); - if (dataTestId === ignoredDataTestId) { return; } + if (dataTestId === ignoredDataTestId) { + return; + } if (dataTestId !== undefined) { dataTestIds.add(dataTestId); - } - else { + } else { attrsMissingIds.add(getAttributeRowName($, $row)); } }); @@ -139,7 +152,7 @@ const processDocumentationInExampleFiles = function (exampleFiles, exampleCovera existingTestIds: dataTestIds, missingTests: new Set(dataTestIds), missingAttrs: attrsMissingIds, - missingKeys: keysMissingIds + missingKeys: keysMissingIds, }; } }; @@ -159,7 +172,14 @@ const getRegressionTestCoverage = function (exampleCoverage) { allTestFiles.push(path.join(testsPath, testfile)); }); - const cmd = path.resolve(__dirname, '..', '..', 'node_modules', 'ava', 'cli.js'); + const cmd = path.resolve( + __dirname, + '..', + '..', + 'node_modules', + 'ava', + 'cli.js' + ); const cmdargs = [...allTestFiles, '--tap', '-c', '1']; const output = spawnSync(cmd, cmdargs); @@ -176,7 +196,7 @@ const getRegressionTestCoverage = function (exampleCoverage) { let testRegex = /^# (\S+) [>›] (\S+\.html) \[data-test-id="(\S+)"\]/gm; let matchResults; // eslint-disable-next-line no-cond-assign - while (matchResults = testRegex.exec(avaResults)) { + while ((matchResults = testRegex.exec(avaResults))) { let example = matchResults[2]; let dataTestId = matchResults[3]; @@ -217,8 +237,7 @@ for (let example in exampleCoverage) { if (existingTestIds === missingTests) { examplesWithNoTestsReport += exampleName + '\n'; examplesWithNoTests++; - } - else if (missingTests) { + } else if (missingTests) { examplesMissingSomeTestsReport += exampleName + ':\n'; for (let testId of exampleCoverage[example].missingTests) { @@ -248,10 +267,8 @@ for (let example in exampleCoverage) { } } - let badFileNames = []; fs.readdirSync(testsPath).forEach(function (testFile) { - let dirName = testFile.split('_')[0]; let dir = path.join(examplePath, dirName); @@ -269,17 +286,30 @@ console.log(missingTestIdsReport || 'None found.\n'); console.log('SUMMARTY:\n'); console.log(' ' + exampleFiles.length + ' example pages found.'); -console.log(' ' + examplesWithNoTests + ' example pages have no regression tests.'); -console.log(' ' + examplesMissingSomeTests + ' example pages are missing approximately ' + - missingTestIds + ' out of approximately ' + totalTestIds + ' tests.\n'); +console.log( + ' ' + examplesWithNoTests + ' example pages have no regression tests.' +); +console.log( + ' ' + + examplesMissingSomeTests + + ' example pages are missing approximately ' + + missingTestIds + + ' out of approximately ' + + totalTestIds + + ' tests.\n' +); if (examplesMissingSomeTests) { - console.log('ERROR - missing tests:\n\n Please write missing tests for this report to pass.\n'); + console.log( + 'ERROR - missing tests:\n\n Please write missing tests for this report to pass.\n' + ); process.exitCode = 1; } if (badFileNames.length) { - console.log('ERROR - bad file names:\n\n Some test files do not follow the correct naming convention. Test file names should begin with the root parent directory of example being tested followed by an underscore (\'_\'). Please correct the following bad test file(s):\n'); + console.log( + "ERROR - bad file names:\n\n Some test files do not follow the correct naming convention. Test file names should begin with the root parent directory of example being tested followed by an underscore ('_'). Please correct the following bad test file(s):\n" + ); for (let file of badFileNames) { console.log(' ' + file[0]); diff --git a/test/util/start-geckodriver.js b/test/util/start-geckodriver.js index 9ced13363e..2e4a32a9fe 100644 --- a/test/util/start-geckodriver.js +++ b/test/util/start-geckodriver.js @@ -10,9 +10,9 @@ const SERIES_LOCK = 8432; const startOnPort = (port, timeout) => { if (timeout < 0) { - return Promise.reject(new Error( - 'Timed out while locating free port for WebDriver server' - )); + return Promise.reject( + new Error('Timed out while locating free port for WebDriver server') + ); } const start = Date.now(); @@ -27,7 +27,7 @@ const startOnPort = (port, timeout) => { child.on('close', giveUp); - (function poll () { + (function poll() { if (stopPolling) { return; } @@ -45,20 +45,19 @@ const startOnPort = (port, timeout) => { resolve(() => child.kill()); }) .catch(() => setTimeout(poll, 500)); - }()); + })(); }); }; const startOnAnyPort = (port, timeout) => { const start = Date.now(); - return startOnPort(port, timeout) - .then(function (stop) { - if (!stop) { - return startOnAnyPort(port + 1, timeout - (Date.now() - start)); - } - return { stop, port }; - }); + return startOnPort(port, timeout).then(function (stop) { + if (!stop) { + return startOnAnyPort(port + 1, timeout - (Date.now() - start)); + } + return { stop, port }; + }); }; /** From cdf213f2fe2cafefc6b48d5b3ac8ab13d933923b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 11:01:22 -0700 Subject: [PATCH 26/34] Infrastructure: Bump cspell package from 4.1.0 to 4.1.2 (pull #1574) Bumps [cspell](https://github.com/streetsidesoftware/cspell) from 4.1.0 to 4.1.2. - [Release notes](https://github.com/streetsidesoftware/cspell/releases) - [Commits](https://github.com/streetsidesoftware/cspell/compare/cspell@4.1.0...cspell@4.1.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 175 ++++++++++++++++++---------------------------- package.json | 2 +- 2 files changed, 68 insertions(+), 109 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac9c54725a..61aace484f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1546,71 +1546,31 @@ "dev": true }, "cspell": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-4.1.0.tgz", - "integrity": "sha512-Wnf8Fz7OZgzM9Z4PzR3Wuu4yGLE7y6sGoQnJH7XZ/UtZC42FnycjcTA8X9qaEKehm/s2imPEMBNuGK0NJealjA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-4.1.2.tgz", + "integrity": "sha512-K/JLpd2YyO1xOModcu6A/WjAf1wZPlH0CECg9p3F7XokGKqqhf0EJNpHh/0ryTn/Nxl20U8sMVCh9Sibt9Z0oA==", "dev": true, "requires": { - "chalk": "^2.4.2", + "chalk": "^4.1.0", "commander": "^6.0.0", "comment-json": "^3.0.3", - "cspell-glob": "^0.1.20", - "cspell-lib": "^4.2.0", + "cspell-glob": "^0.1.21", + "cspell-lib": "^4.2.2", "fs-extra": "^9.0.1", "gensequence": "^3.1.1", - "get-stdin": "^7.0.0", + "get-stdin": "^8.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } } } @@ -1643,9 +1603,9 @@ } }, "cspell-dict-cpp": { - "version": "1.1.27", - "resolved": "https://registry.npmjs.org/cspell-dict-cpp/-/cspell-dict-cpp-1.1.27.tgz", - "integrity": "sha512-B6BKxk/HK2PT5S0354Lf8rxdazGTABoQ3GOvgXXuxX53tUtP2jVguaBQGUt67aIMFN9hYjTC4SsYBlvWZ2Fiaw==", + "version": "1.1.28", + "resolved": "https://registry.npmjs.org/cspell-dict-cpp/-/cspell-dict-cpp-1.1.28.tgz", + "integrity": "sha512-lr53hT4LNWfFcH2q3NWqwnI5JtfiIrZt60AkVWnaumfIV7MO7qcWPSohhqSQW2VF2idx9rH6iK6/UwKz0RzLZw==", "dev": true, "requires": { "configstore": "^5.0.1" @@ -1688,18 +1648,18 @@ } }, "cspell-dict-en-gb": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/cspell-dict-en-gb/-/cspell-dict-en-gb-1.1.17.tgz", - "integrity": "sha512-RJJiRVB1QkkYSBAxNgais07R9IPeIwTLFS+Xi+CL99YCdwQoQtp4RbBZTFYPN+wWAEupN247FK0d52FYDhVI0g==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/cspell-dict-en-gb/-/cspell-dict-en-gb-1.1.19.tgz", + "integrity": "sha512-GB7Dw6yEZdz/ajNoEkunLhkCQL4+KH8owVFSZG27h/GD0wwkReOd/iKNvctCZFhZNYQDj90l+zZD9J7j2IURVA==", "dev": true, "requires": { "configstore": "^5.0.1" } }, "cspell-dict-en_us": { - "version": "1.2.27", - "resolved": "https://registry.npmjs.org/cspell-dict-en_us/-/cspell-dict-en_us-1.2.27.tgz", - "integrity": "sha512-wswqZLa7KV7tb9zS6oNA1s+VAehb1c76GloikexGC6A98n6EkoiSi+y9FDn9WXQPVlfrSAioLDVijPPF0Czo2Q==", + "version": "1.2.29", + "resolved": "https://registry.npmjs.org/cspell-dict-en_us/-/cspell-dict-en_us-1.2.29.tgz", + "integrity": "sha512-cnfsQI/9vVXyodK3DIMwmVEv1H8dvHR62Du0gIbOeYAEGapMZnWW4UdXaeP4TGWOwBocrPm165K9djSZG0TT+Q==", "dev": true, "requires": { "configstore": "^5.0.1" @@ -1769,9 +1729,9 @@ } }, "cspell-dict-lorem-ipsum": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/cspell-dict-lorem-ipsum/-/cspell-dict-lorem-ipsum-1.0.12.tgz", - "integrity": "sha512-CAJJH8Fwm+itcRMDUCWdcL43120b91umWwkotvkxjWmlxrTl57aIveC/RGksQ6lSaymPNL7jg9K68YoKF7DMRw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/cspell-dict-lorem-ipsum/-/cspell-dict-lorem-ipsum-1.0.13.tgz", + "integrity": "sha512-B/1C4hKRg5Trnm6SNSw6XbYASoggHrBzUzhV+g/MxDVHIHazESLXO1nfJIVCooKSQwxBRnrn2uEtX4ryIdyVBQ==", "dev": true, "requires": { "configstore": "^5.0.1" @@ -1805,9 +1765,9 @@ } }, "cspell-dict-python": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/cspell-dict-python/-/cspell-dict-python-1.0.21.tgz", - "integrity": "sha512-RQYvsxD40YGr6I8vkuTiWlXy4ccVT5F3+kIZwikqk/NeN7/1SuiRVBpAzRkzh+X+IW3RlxBfs2TqcIjWVp6Tjg==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/cspell-dict-python/-/cspell-dict-python-1.0.22.tgz", + "integrity": "sha512-ddLd+MPQPkVAe3CveCLAe2JQtuV1nqod2nnrXabdspD2Q4yQI/iEj3oMe1Ig4N63ZGZcn0MirC7k2Dcy4uGKsQ==", "dev": true, "requires": { "configstore": "^5.0.1" @@ -1841,9 +1801,9 @@ } }, "cspell-dict-software-terms": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/cspell-dict-software-terms/-/cspell-dict-software-terms-1.0.12.tgz", - "integrity": "sha512-2jPDJoGRpbhuB4MGeau/zdy7fzRqIKSy0vMQPGVs7Z7wLZ7QsqJw827veUBL2ZHPelBllkeBcIN5hhuSuAfNdg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/cspell-dict-software-terms/-/cspell-dict-software-terms-1.0.13.tgz", + "integrity": "sha512-djSM4y7JrlL2tibSGhig2j8kKYjgi24V3n1pI7S99oRDy7MCiFeHBnN5zcJFlWSUFjN7u2K/IanNF2M6Lu+UiQ==", "dev": true, "requires": { "configstore": "^5.0.1" @@ -1859,39 +1819,28 @@ } }, "cspell-glob": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-0.1.20.tgz", - "integrity": "sha512-pXKdl6m7WjGqQNxv4T1II+hxDvzqZHHcSjLZL9kgszlimpIriNiTN7PRg0oFbda0fyIWJ9CuLo7AbNuGnJbZQg==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-0.1.21.tgz", + "integrity": "sha512-+xwxxB0QZRKKgehZP0Jp48mBKiYa3RkGb0XgxcBrEZr9u0GBLOYUDi6iDox9VR+/rihvUvQKt8tTRGsMyNKe0A==", "dev": true, "requires": { "micromatch": "^4.0.2" } }, "cspell-io": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-4.1.0.tgz", - "integrity": "sha512-FOkS/VFTJPmipB2ObG94VdoBqNuH0hqZpyMPy7HVuAiY18hx0L13F43ZxpLHDEKfMVtRQfB4HCYAffm6yMUzRw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-4.1.1.tgz", + "integrity": "sha512-KCdjroahNSQRrev06As05tF/tQ93EwVb06ziCKpEYI7kBt+ZpN3+vqYxJh9pj8TQs52TvZFhNVySR9NS9aDb2g==", "dev": true, "requires": { "iconv-lite": "^0.6.2", "iterable-to-stream": "^1.0.1" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } } }, "cspell-lib": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-4.2.0.tgz", - "integrity": "sha512-JmH6c5/lRUxZ5noEUv0rSYtfJAcx6qrrFF/1cuj4uesABIS2mcg/HcAerByzTRvl2AU5kvQu3erEF2cttbrZQQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-4.2.2.tgz", + "integrity": "sha512-mXOHV9ipIuXHt2jx/u2dnrvIHpf5fYGOFD80DS/lO6pQ4O+hJ/ZI0KwGJdwv63U4GBYAkHLM9mMIqtBtVM182A==", "dev": true, "requires": { "comment-json": "^3.0.3", @@ -1899,13 +1848,13 @@ "cspell-dict-aws": "^1.0.6", "cspell-dict-bash": "^1.0.4", "cspell-dict-companies": "^1.0.23", - "cspell-dict-cpp": "^1.1.27", + "cspell-dict-cpp": "^1.1.28", "cspell-dict-cryptocurrencies": "^1.0.3", "cspell-dict-django": "^1.0.16", "cspell-dict-dotnet": "^1.0.15", "cspell-dict-elixir": "^1.0.14", - "cspell-dict-en-gb": "^1.1.17", - "cspell-dict-en_us": "^1.2.27", + "cspell-dict-en-gb": "^1.1.19", + "cspell-dict-en_us": "^1.2.29", "cspell-dict-fonts": "^1.0.6", "cspell-dict-fullstack": "^1.0.24", "cspell-dict-golang": "^1.1.15", @@ -1913,38 +1862,39 @@ "cspell-dict-html-symbol-entities": "^1.0.14", "cspell-dict-java": "^1.0.13", "cspell-dict-latex": "^1.0.14", - "cspell-dict-lorem-ipsum": "^1.0.12", + "cspell-dict-lorem-ipsum": "^1.0.13", "cspell-dict-lua": "^1.0.9", "cspell-dict-php": "^1.0.14", "cspell-dict-powershell": "^1.0.7", - "cspell-dict-python": "^1.0.21", + "cspell-dict-python": "^1.0.22", "cspell-dict-ruby": "^1.0.4", "cspell-dict-rust": "^1.0.13", "cspell-dict-scala": "^1.0.12", - "cspell-dict-software-terms": "^1.0.11", + "cspell-dict-software-terms": "^1.0.13", "cspell-dict-typescript": "^1.0.6", - "cspell-io": "^4.1.0", - "cspell-trie-lib": "^4.2.0", - "cspell-util-bundle": "^4.1.0", + "cspell-io": "^4.1.1", + "cspell-trie-lib": "^4.2.1", + "cspell-util-bundle": "^4.1.1", "fs-extra": "^9.0.1", "gensequence": "^3.1.1", "minimatch": "^3.0.4", + "resolve-from": "^5.0.0", "vscode-uri": "^2.1.2" } }, "cspell-trie-lib": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-4.2.0.tgz", - "integrity": "sha512-yovSTcupq1T/PWr/oVjV3rAB0r80f/r62kkVk3dluICPUKtCKIjtmlJqqRR12X6gEOxWKromaT4No3WF+c7W6Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-4.2.1.tgz", + "integrity": "sha512-oW3x8K8xXOko7eVRo3B+3Dlu1dwpHp1jtsdI6Zq2POx10WDbobTf8xSIpu8qFAPXFUHKQ5JSM7AUy6auCaPWNw==", "dev": true, "requires": { "gensequence": "^3.1.1" } }, "cspell-util-bundle": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cspell-util-bundle/-/cspell-util-bundle-4.1.0.tgz", - "integrity": "sha512-mxVrlCoUIyE6yQIHgsvJTDq6N0yH4TOnL0eHS/7MpWu+n7kQVgR7Kndn3RWln3tZTXzvFMp9yNLSE06Ema5jQA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/cspell-util-bundle/-/cspell-util-bundle-4.1.1.tgz", + "integrity": "sha512-ROeJfdekgq4zE/+hFzFryCqnnR9D3ncm00R4FlGVwPJsONzrkZGnYVGViQKN6f+VyQAEHjTWj4G4uLcnySehyQ==", "dev": true }, "css-select": { @@ -2773,9 +2723,9 @@ "dev": true }, "get-stdin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", - "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", "dev": true }, "get-stream": { @@ -3000,6 +2950,15 @@ "which-pm-runs": "^1.0.0" } }, + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "ignore": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", diff --git a/package.json b/package.json index d8ba5cf0e0..098f52a98b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "devDependencies": { "ava": "^3.13.0", "cheerio": "^1.0.0-rc.2", - "cspell": "^4.1.0", + "cspell": "^4.1.2", "eslint": "^7.10.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-prettier": "^3.1.4", From 8931a62395a37eb7d01d3f6a1085b8d6bbb99215 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 11:40:05 -0700 Subject: [PATCH 27/34] Infrastructure: Bump eslint package from 7.10.0 to 7.11.0 (pull #1565) Bumps [eslint](https://github.com/eslint/eslint) from 7.10.0 to 7.11.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v7.10.0...v7.11.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 42 +++++++++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 61aace484f..52272aff59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -674,9 +674,9 @@ "dev": true }, "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { @@ -717,9 +717,9 @@ } }, "ajv": { - "version": "6.12.5", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", - "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -2217,9 +2217,9 @@ "dev": true }, "eslint": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz", - "integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz", + "integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -2232,7 +2232,7 @@ "enquirer": "^2.3.5", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^1.3.0", + "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.0", "esquery": "^1.2.0", "esutils": "^2.0.2", @@ -2324,12 +2324,20 @@ "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", "dev": true }, "espree": { @@ -2341,6 +2349,14 @@ "acorn": "^7.4.0", "acorn-jsx": "^5.2.0", "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "esprima": { diff --git a/package.json b/package.json index 098f52a98b..039c4aab4c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "ava": "^3.13.0", "cheerio": "^1.0.0-rc.2", "cspell": "^4.1.2", - "eslint": "^7.10.0", + "eslint": "^7.11.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-prettier": "^3.1.4", "geckodriver": "^1.20.0", From 81c92c30bbd4c508957b831d8c4ed54888236ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2020 12:08:46 -0700 Subject: [PATCH 28/34] Infrastructure: Bump lint-staged package from 10.4.0 to 10.4.2 (pull #1575) Bumps [lint-staged](https://github.com/okonet/lint-staged) from 10.4.0 to 10.4.2. - [Release notes](https://github.com/okonet/lint-staged/releases) - [Commits](https://github.com/okonet/lint-staged/compare/v10.4.0...v10.4.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 52 ++++++----------------------------------------- package.json | 2 +- 2 files changed, 7 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52272aff59..7b45bcd506 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3465,9 +3465,9 @@ "dev": true }, "lint-staged": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.4.0.tgz", - "integrity": "sha512-uaiX4U5yERUSiIEQc329vhCTDDwUcSvKdRLsNomkYLRzijk3v8V9GWm2Nz0RMVB87VcuzLvtgy6OsjoH++QHIg==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.4.2.tgz", + "integrity": "sha512-OLCA9K1hS+Sl179SO6kX0JtnsaKj/MZalEhUj5yAgXsb63qPI/Gfn6Ua1KuZdbfkZNEu3/n5C/obYCu70IMt9g==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -3496,46 +3496,6 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } - }, - "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", - "dev": true } } }, @@ -6049,9 +6009,9 @@ "dev": true }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, "type-check": { diff --git a/package.json b/package.json index 039c4aab4c..aec57605d5 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "geckodriver": "^1.20.0", "glob": "^7.1.6", "husky": "^4.3.0", - "lint-staged": "^10.4.0", + "lint-staged": "^10.4.2", "npm-merge-driver": "^2.3.6", "prettier": "^2.1.2", "run-node": "^2.0.0", From 6485d4c3aa33f6212fd9bcd7b6555d3eac06d31e Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 21 Oct 2020 16:38:56 -0400 Subject: [PATCH 29/34] Infrastructure: Switch HTML linting to GitHub Actions (pull #1523) * chore: Switch HTML linting to GitHub Actions * chore: no-op Travis run till it can be removed --- .github/workflows/lint-html.yml | 40 +++++++++++++++++++ .../workflows/vnu-jar-problem-matcher.json | 17 ++++++++ .travis.yml | 10 +---- 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/lint-html.yml create mode 100644 .github/workflows/vnu-jar-problem-matcher.json diff --git a/.github/workflows/lint-html.yml b/.github/workflows/lint-html.yml new file mode 100644 index 0000000000..53a85e2d94 --- /dev/null +++ b/.github/workflows/lint-html.yml @@ -0,0 +1,40 @@ +name: HTML Linting +on: + push: + branches-ignore: + - "dependabot/**" + paths: + - "package*.json" + - ".vnurc" + - "aria-practices.html" + - "examples/**/*.html" + - ".github/workflows/lint-html.yml" + - ".github/workflows/vnu-jar-problem-matcher.json" + + pull_request: + paths: + - "package*.json" + - ".vnurc" + - "aria-practices.html" + - "examples/**/*.html" + - ".github/workflows/lint-html.yml" + - ".github/workflows/vnu-jar-problem-matcher.json" + +jobs: + lint-html: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2.1.2 + + - name: Install npm dependencies + run: npm ci + + - name: HTML Validator + run: | + echo "::add-matcher::.github/workflows/vnu-jar-problem-matcher.json" + npm run lint:html diff --git a/.github/workflows/vnu-jar-problem-matcher.json b/.github/workflows/vnu-jar-problem-matcher.json new file mode 100644 index 0000000000..d4cb3cccc6 --- /dev/null +++ b/.github/workflows/vnu-jar-problem-matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "vnu-jar", + "pattern": [ + { + "regexp": "^\"file:(.*)\":(\\d+).(\\d+)-\\d+\\.\\d+:\\s(error|warning):\\s(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + ] + } + ] +} diff --git a/.travis.yml b/.travis.yml index a4b32dd7da..bab72fe76c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,6 @@ node_js: git: depth: 3 -stages: -- Lint +install: true -jobs: - - include: - - stage: Lint - name: HTML Linting - script: npm run vnu-jar +script: true From a9bb774074592b9ada5a63361c04ba8201803167 Mon Sep 17 00:00:00 2001 From: Valerie Young Date: Thu, 22 Oct 2020 15:28:16 -0700 Subject: [PATCH 30/34] Infrastructure: Update vnujar filtering to restore ignoring no alt on role=img (pull #1468) * Update vnujar filtering to ignore no alt on role=img, again * Update vnu-jar * Explicitly write both version of vnu-jar errors for alt errors * Update .vnurc, remove old error * Update .vnurc, escape asterix Co-authored-by: Nick Schonning Co-authored-by: Matt King ` --- .vnurc | 4 +++- package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.vnurc b/.vnurc index 9574520e99..4afaa5da84 100644 --- a/.vnurc +++ b/.vnurc @@ -4,7 +4,9 @@ # Proposed, tracking in gh-429 Bad value “” for attribute “aria-activedescendant” on element “ul”:.* # Ignoring for Slider examples because it is forcing rule with role=img -Element “img” is missing required attribute “alt”. +An “img” element with no “alt” attribute must not have a “role” attribute. +An “img” element with no “alt” attribute must not have any “aria-\*” attributes other than “aria-hidden”. +An “img” element must have an “alt” attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images. # Ignoring aria-posinset and aria-setsize on role row Attribute “aria-posinset” not allowed on element “tr” at this point. Attribute “aria-setsize” not allowed on element “tr” at this point. diff --git a/package-lock.json b/package-lock.json index 7b45bcd506..8e22cdfc99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6254,9 +6254,9 @@ } }, "vnu-jar": { - "version": "20.3.16", - "resolved": "https://registry.npmjs.org/vnu-jar/-/vnu-jar-20.3.16.tgz", - "integrity": "sha512-CHlvP4lilQvgjSRDsiB7i0lN0gnkq6zv3sN/71FYVgOQ/nYLXFfUhGIVeGskm9W1C0+UralU1XQnbsjJr2ijaw==", + "version": "20.6.30", + "resolved": "https://registry.npmjs.org/vnu-jar/-/vnu-jar-20.6.30.tgz", + "integrity": "sha512-zlNNe7jW6cTIrxVlZK9AcZiNWjxzjqi7LpTafBCi4OY4bsh2uyGhxXT2l6hZ3ibpxP0ZC/pQsBpFQEACS0ePIg==", "dev": true }, "vscode-uri": { diff --git a/package.json b/package.json index aec57605d5..dae64fe7f7 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "stylelint-config-prettier": "^8.0.2", "stylelint-config-standard": "^20.0.0", "stylelint-prettier": "^1.1.2", - "vnu-jar": "^20.3.16" + "vnu-jar": "^20.6.30" }, "husky": { "hooks": { From c4d8d143169ac33bf1cf6a607ca394260969efd8 Mon Sep 17 00:00:00 2001 From: Valerie Young Date: Thu, 22 Oct 2020 16:42:41 -0700 Subject: [PATCH 31/34] Infrastructure: Format PR comments containing regression coverage reports with markdown (pull #1562) The regression test coverage report lists keyboard commands, roles, and states missing tests. It runs against pull requests and adds a comment containing the report. This commit implements formatting of those PR comments using markdown. Co-authored-by: Matt King --- .github/workflows/coverage-report.yml | 2 +- test/util/report.js | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/coverage-report.yml b/.github/workflows/coverage-report.yml index 35955505f5..be405bc093 100644 --- a/.github/workflows/coverage-report.yml +++ b/.github/workflows/coverage-report.yml @@ -29,7 +29,7 @@ jobs: github-token: ${{secrets.GITHUB_TOKEN}} script: | const fs = require('fs'); - const commentBody = '```' + fs.readFileSync('coverage.log', 'utf8') + '```'; + const commentBody = fs.readFileSync('coverage.log', 'utf8'); if (commentBody.length === 0) { return diff --git a/test/util/report.js b/test/util/report.js index 2780f1647a..6a74fe8318 100644 --- a/test/util/report.js +++ b/test/util/report.js @@ -235,13 +235,13 @@ for (let example in exampleCoverage) { let exampleName = example; if (existingTestIds === missingTests) { - examplesWithNoTestsReport += exampleName + '\n'; + examplesWithNoTestsReport += '- ' + exampleName + '\n'; examplesWithNoTests++; } else if (missingTests) { - examplesMissingSomeTestsReport += exampleName + ':\n'; + examplesMissingSomeTestsReport += '- ' + exampleName + ':\n'; for (let testId of exampleCoverage[example].missingTests) { - examplesMissingSomeTestsReport += ' ' + testId + '\n'; + examplesMissingSomeTestsReport += ' - ' + testId + '\n'; } examplesMissingSomeTests += 1; @@ -249,18 +249,18 @@ for (let example in exampleCoverage) { } if (missingKeys || missingAttrs) { - missingTestIdsReport += exampleName + '\n'; + missingTestIdsReport += '- ' + exampleName + '\n'; if (missingKeys) { - missingTestIdsReport += ' "Keyboard Support" table(s):\n'; + missingTestIdsReport += ' - "Keyboard Support" table(s):\n'; for (let row of exampleCoverage[example].missingKeys) { - missingTestIdsReport += ' ' + row + '\n'; + missingTestIdsReport += ' - ' + row + '\n'; } } if (missingAttrs) { - missingTestIdsReport += ' "Attributes" table(s):\n'; + missingTestIdsReport += ' - "Attributes" table(s):\n'; for (let row of exampleCoverage[example].missingAttrs) { - missingTestIdsReport += ' ' + row + '\n'; + missingTestIdsReport += ' - ' + row + '\n'; } } } @@ -277,14 +277,17 @@ fs.readdirSync(testsPath).forEach(function (testFile) { } }); -console.log('\nExamples without any regression tests:\n'); +console.log('\n#### Regression test coverage:\n'); +console.log('\n#### Examples without any regression tests:\n'); console.log(examplesWithNoTestsReport || 'None found.\n'); -console.log('\nExamples missing some regression tests:\n'); +console.log('\n#### Examples missing some regression tests:\n'); console.log(examplesMissingSomeTestsReport || 'None found.\n'); -console.log('\nExamples documentation table rows without data-test-ids:\n'); +console.log( + '\n#### Example pages with Keyboard or Attribute table rows that do not have data-test-ids:\n' +); console.log(missingTestIdsReport || 'None found.\n'); -console.log('SUMMARTY:\n'); +console.log('#### SUMMARY:\n'); console.log(' ' + exampleFiles.length + ' example pages found.'); console.log( ' ' + examplesWithNoTests + ' example pages have no regression tests.' From 89a5abf5af2f58df3d229e7ddd65087f372c2c59 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Thu, 22 Oct 2020 20:43:44 -0400 Subject: [PATCH 32/34] Infrastructure: add Ava ESLint plug-in with recommended rules (pull #1566) * add ava ESLint plug-in * fix: Ava ESLint recommended cleanups * No strict declaration in Ava module files * Ignore the "index" test file as it isn't directly called * Fix async/await functions * `after.always` hook must come before `beforeEach` --- package-lock.json | 69 +++++++++++++++++++ package.json | 1 + test/.eslintrc.json | 2 +- test/index.js | 20 +++--- test/tests/accordion_accordion.js | 2 - test/tests/alert_alert.js | 2 - test/tests/breadcrumb_index.js | 2 - test/tests/button_button.js | 2 - test/tests/carousel_carousel-1-prev-next.js | 2 - test/tests/carousel_carousel-2-tablist.js | 2 - test/tests/checkbox_checkbox-1.js | 2 - test/tests/checkbox_checkbox-2.js | 2 - test/tests/combobox_autocomplete-both.js | 2 - test/tests/combobox_autocomplete-list.js | 2 - test/tests/combobox_autocomplete-none.js | 2 - test/tests/combobox_datepicker.js | 2 - test/tests/combobox_grid-combo.js | 2 - test/tests/combobox_select-only.js | 2 - test/tests/dialog-modal_datepicker.js | 2 - test/tests/dialog-modal_dialog.js | 2 - test/tests/disclosure_faq.js | 2 - test/tests/disclosure_img-long-description.js | 2 - test/tests/disclosure_navigation.js | 2 - test/tests/feed_feed.js | 2 - test/tests/grid_LayoutGrids.js | 2 - test/tests/grid_dataGrids.js | 2 - test/tests/link_link.js | 2 - test/tests/listbox_collapsible.js | 2 - test/tests/listbox_grouped.js | 2 - test/tests/listbox_rearrangeable.js | 2 - test/tests/listbox_scrollable.js | 2 - .../menu-button_actions-active-descendant.js | 2 - test/tests/menu-button_actions.js | 2 - test/tests/menu-button_links.js | 2 - test/tests/menubar_menubar-editor.js | 2 - test/tests/menubar_menubar-navigation.js | 2 - test/tests/meter_meter.js | 2 - test/tests/radio_radio-activedescendant.js | 2 - test/tests/radio_radio.js | 2 - test/tests/slider_multithumb.js | 2 - test/tests/slider_slider-1.js | 2 - test/tests/slider_slider-2.js | 2 - test/tests/spinbutton_datepicker.js | 2 - test/tests/table_table.js | 2 - test/tests/tabs_tabs-1.js | 2 - test/tests/tabs_tabs-2.js | 2 - test/tests/toolbar_toolbar.js | 2 - test/tests/treegrid_treegrid-1.js | 2 - test/tests/treeview_treeview-1a.js | 2 - test/tests/treeview_treeview-1b.js | 2 - test/tests/treeview_treeview-2a.js | 2 - test/tests/treeview_treeview-2b.js | 2 - test/util/assertAriaActivedescendant.js | 2 - test/util/assertAriaControls.js | 2 - test/util/assertAriaDescribedby.js | 2 - test/util/assertAriaLabelExists.js | 2 - test/util/assertAriaLabelledby.js | 2 - test/util/assertAriaRoles.js | 2 - .../assertAriaSelectedAndActivedescendant.js | 2 - test/util/assertAttributeDNE.js | 2 - test/util/assertAttributeValues.js | 2 - test/util/assertHasFocus.js | 2 - test/util/assertNoElements.js | 5 +- test/util/assertRovingTabindex.js | 2 - test/util/assertTabOrder.js | 2 - test/util/force-serial.js | 1 - test/util/get-json.js | 2 - test/util/queryElement.js | 2 - test/util/queryElements.js | 5 +- test/util/report.js | 1 - test/util/start-geckodriver.js | 2 - 71 files changed, 85 insertions(+), 145 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e22cdfc99..fa1620c250 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1084,6 +1084,12 @@ "node-releases": "^1.1.61" } }, + "buf-compare": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", + "integrity": "sha1-/vKNqLgROgoNtEMLC2Rntpcws0o=", + "dev": true + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1475,6 +1481,16 @@ "integrity": "sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU=", "dev": true }, + "core-assert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/core-assert/-/core-assert-0.2.1.tgz", + "integrity": "sha1-+F4s+b/tKPdzzIs/pcW2m9wC/j8=", + "dev": true, + "requires": { + "buf-compare": "^1.0.0", + "is-error": "^2.2.0" + } + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1999,6 +2015,15 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deep-strict-equal": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/deep-strict-equal/-/deep-strict-equal-0.2.0.tgz", + "integrity": "sha1-SgeBR6irV/ag1PVUckPNIvROtOQ=", + "dev": true, + "requires": { + "core-assert": "^0.2.0" + } + }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -2168,6 +2193,15 @@ "once": "^1.4.0" } }, + "enhance-visitors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/enhance-visitors/-/enhance-visitors-1.0.0.tgz", + "integrity": "sha1-qpRdBdpGVnKh69OP7i7T2oUY6Vo=", + "dev": true, + "requires": { + "lodash": "^4.13.1" + } + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -2298,6 +2332,23 @@ } } }, + "eslint-plugin-ava": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-ava/-/eslint-plugin-ava-11.0.0.tgz", + "integrity": "sha512-UMGedfl/gIKx1tzjGtAsTSJgowyAEZU2VWmpoWXYcuuV4B2H4Cu90yuMgMPEVt1mQlIZ21L7YM2CSpHUFJo/LQ==", + "dev": true, + "requires": { + "deep-strict-equal": "^0.2.0", + "enhance-visitors": "^1.0.0", + "eslint-utils": "^2.1.0", + "espree": "^7.2.0", + "espurify": "^2.0.1", + "import-modules": "^2.0.0", + "micro-spelling-correcter": "^1.1.1", + "pkg-dir": "^4.2.0", + "resolve-from": "^5.0.0" + } + }, "eslint-plugin-prettier": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", @@ -2365,6 +2416,12 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "espurify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/espurify/-/espurify-2.0.1.tgz", + "integrity": "sha512-7w/dUrReI/QbJFHRwfomTlkQOXaB1NuCrBRn5Y26HXn5gvh18/19AgLbayVrNxXQfkckvgrJloWyvZDuJ7dhEA==", + "dev": true + }, "esquery": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", @@ -3027,6 +3084,12 @@ "resolve-cwd": "^3.0.0" } }, + "import-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-modules/-/import-modules-2.0.0.tgz", + "integrity": "sha512-iczM/v9drffdNnABOKwj0f9G3cFDon99VcG1mxeBsdqnbd+vnQ5c2uAiCHNQITqFTOPaEvwg3VjoWCur0uHLEw==", + "dev": true + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3756,6 +3819,12 @@ "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", "dev": true }, + "micro-spelling-correcter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/micro-spelling-correcter/-/micro-spelling-correcter-1.1.1.tgz", + "integrity": "sha512-lkJ3Rj/mtjlRcHk6YyCbvZhyWTOzdBvTHsxMmZSk5jxN1YyVSQ+JETAom55mdzfcyDrY/49Z7UCW760BK30crg==", + "dev": true + }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", diff --git a/package.json b/package.json index dae64fe7f7..d12296594d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "cspell": "^4.1.2", "eslint": "^7.11.0", "eslint-config-prettier": "^6.12.0", + "eslint-plugin-ava": "^11.0.0", "eslint-plugin-prettier": "^3.1.4", "geckodriver": "^1.20.0", "glob": "^7.1.6", diff --git a/test/.eslintrc.json b/test/.eslintrc.json index 0af4dc9dff..dd6115537c 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": "../.eslintrc.json", + "extends": ["../.eslintrc.json", "plugin:ava/recommended"], "parserOptions": { "ecmaVersion": 8 } diff --git a/test/index.js b/test/index.js index 921650e523..01bb7b7142 100644 --- a/test/index.js +++ b/test/index.js @@ -1,4 +1,4 @@ -'use strict'; +/* eslint-disable ava/no-ignored-test-files */ const path = require('path'); const test = require('ava'); @@ -29,18 +29,22 @@ if (!coverageReportRun) { await session; }); + test.after.always(async () => { + if (session) { + await session.close(); + } + + if (geckodriver) { + await geckodriver.stop(); + } + }); + test.beforeEach((t) => { t.context.session = session; t.context.waitTime = testWaitTime; t.context.queryElement = queryElement; t.context.queryElements = queryElements; }); - - test.after.always(() => { - return Promise.resolve(session && session.close()).then( - () => geckodriver && geckodriver.stop() - ); - }); } /** @@ -75,7 +79,7 @@ const _ariaTest = (desc, page, testId, body, failing) => { const testName = page + ' ' + selector + ': ' + desc; if (coverageReportRun) { - test(testName, async function (t) { + test(testName, function (t) { t.fail('All tests expect to fail. Running in coverage mode.'); }); return; diff --git a/test/tests/accordion_accordion.js b/test/tests/accordion_accordion.js index 7de79f683e..59cb266c82 100644 --- a/test/tests/accordion_accordion.js +++ b/test/tests/accordion_accordion.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaControls = require('../util/assertAriaControls'); diff --git a/test/tests/alert_alert.js b/test/tests/alert_alert.js index 728324db4e..c58f470031 100644 --- a/test/tests/alert_alert.js +++ b/test/tests/alert_alert.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By } = require('selenium-webdriver'); diff --git a/test/tests/breadcrumb_index.js b/test/tests/breadcrumb_index.js index 598f5c991e..e1852bcaab 100644 --- a/test/tests/breadcrumb_index.js +++ b/test/tests/breadcrumb_index.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By } = require('selenium-webdriver'); const assertAriaLabelExists = require('../util/assertAriaLabelExists'); diff --git a/test/tests/button_button.js b/test/tests/button_button.js index 502869ac6b..6c0fa593e2 100644 --- a/test/tests/button_button.js +++ b/test/tests/button_button.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); diff --git a/test/tests/carousel_carousel-1-prev-next.js b/test/tests/carousel_carousel-1-prev-next.js index 2654a4fee3..918e00c7b2 100644 --- a/test/tests/carousel_carousel-1-prev-next.js +++ b/test/tests/carousel_carousel-1-prev-next.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/carousel_carousel-2-tablist.js b/test/tests/carousel_carousel-2-tablist.js index 51c4909047..2b0b6c9558 100644 --- a/test/tests/carousel_carousel-2-tablist.js +++ b/test/tests/carousel_carousel-2-tablist.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/checkbox_checkbox-1.js b/test/tests/checkbox_checkbox-1.js index dd60628132..02c59ba668 100644 --- a/test/tests/checkbox_checkbox-1.js +++ b/test/tests/checkbox_checkbox-1.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/checkbox_checkbox-2.js b/test/tests/checkbox_checkbox-2.js index 355bfe6236..49111e0fbb 100644 --- a/test/tests/checkbox_checkbox-2.js +++ b/test/tests/checkbox_checkbox-2.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/combobox_autocomplete-both.js b/test/tests/combobox_autocomplete-both.js index c4606fe953..e9dfc19761 100644 --- a/test/tests/combobox_autocomplete-both.js +++ b/test/tests/combobox_autocomplete-both.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/combobox_autocomplete-list.js b/test/tests/combobox_autocomplete-list.js index 30686b7afa..56140ed3ab 100644 --- a/test/tests/combobox_autocomplete-list.js +++ b/test/tests/combobox_autocomplete-list.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/combobox_autocomplete-none.js b/test/tests/combobox_autocomplete-none.js index 42af4b1e05..218e70a827 100644 --- a/test/tests/combobox_autocomplete-none.js +++ b/test/tests/combobox_autocomplete-none.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/combobox_datepicker.js b/test/tests/combobox_datepicker.js index e9b4910c6d..d6a962b98b 100644 --- a/test/tests/combobox_datepicker.js +++ b/test/tests/combobox_datepicker.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/combobox_grid-combo.js b/test/tests/combobox_grid-combo.js index 6f5755b2e0..f021f84a13 100644 --- a/test/tests/combobox_grid-combo.js +++ b/test/tests/combobox_grid-combo.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaLabelledby = require('../util/assertAriaLabelledby'); diff --git a/test/tests/combobox_select-only.js b/test/tests/combobox_select-only.js index f105251736..453954d412 100644 --- a/test/tests/combobox_select-only.js +++ b/test/tests/combobox_select-only.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/dialog-modal_datepicker.js b/test/tests/dialog-modal_datepicker.js index 1fd9a93d3f..7ccf8ef94a 100644 --- a/test/tests/dialog-modal_datepicker.js +++ b/test/tests/dialog-modal_datepicker.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/dialog-modal_dialog.js b/test/tests/dialog-modal_dialog.js index 99fda06ec1..073dffe08b 100644 --- a/test/tests/dialog-modal_dialog.js +++ b/test/tests/dialog-modal_dialog.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/disclosure_faq.js b/test/tests/disclosure_faq.js index 1cfc71fa4a..d695e1535a 100644 --- a/test/tests/disclosure_faq.js +++ b/test/tests/disclosure_faq.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaControls = require('../util/assertAriaControls'); diff --git a/test/tests/disclosure_img-long-description.js b/test/tests/disclosure_img-long-description.js index d801adb7ad..37b9ecb016 100644 --- a/test/tests/disclosure_img-long-description.js +++ b/test/tests/disclosure_img-long-description.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaControls = require('../util/assertAriaControls'); diff --git a/test/tests/disclosure_navigation.js b/test/tests/disclosure_navigation.js index 7376adca87..8d4516657e 100644 --- a/test/tests/disclosure_navigation.js +++ b/test/tests/disclosure_navigation.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaControls = require('../util/assertAriaControls'); diff --git a/test/tests/feed_feed.js b/test/tests/feed_feed.js index 99d35d210b..b2eb33323e 100644 --- a/test/tests/feed_feed.js +++ b/test/tests/feed_feed.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); diff --git a/test/tests/grid_LayoutGrids.js b/test/tests/grid_LayoutGrids.js index 894293786e..84589557f9 100644 --- a/test/tests/grid_LayoutGrids.js +++ b/test/tests/grid_LayoutGrids.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); diff --git a/test/tests/grid_dataGrids.js b/test/tests/grid_dataGrids.js index 5f0ddc35c3..664b875e05 100644 --- a/test/tests/grid_dataGrids.js +++ b/test/tests/grid_dataGrids.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaLabelledby = require('../util/assertAriaLabelledby'); diff --git a/test/tests/link_link.js b/test/tests/link_link.js index b31b8a4dec..f7fdd4d260 100644 --- a/test/tests/link_link.js +++ b/test/tests/link_link.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaLabelExists = require('../util/assertAriaLabelExists'); diff --git a/test/tests/listbox_collapsible.js b/test/tests/listbox_collapsible.js index 8f2aa10ea3..4c551f3699 100644 --- a/test/tests/listbox_collapsible.js +++ b/test/tests/listbox_collapsible.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/listbox_grouped.js b/test/tests/listbox_grouped.js index 5787763ece..0346dfaca7 100644 --- a/test/tests/listbox_grouped.js +++ b/test/tests/listbox_grouped.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/listbox_rearrangeable.js b/test/tests/listbox_rearrangeable.js index f48145ed09..5b1b24f3ff 100644 --- a/test/tests/listbox_rearrangeable.js +++ b/test/tests/listbox_rearrangeable.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/listbox_scrollable.js b/test/tests/listbox_scrollable.js index d1d98a4885..3cd3891a50 100644 --- a/test/tests/listbox_scrollable.js +++ b/test/tests/listbox_scrollable.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/menu-button_actions-active-descendant.js b/test/tests/menu-button_actions-active-descendant.js index 1e3a95f3ee..a97fcbaecf 100644 --- a/test/tests/menu-button_actions-active-descendant.js +++ b/test/tests/menu-button_actions-active-descendant.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/menu-button_actions.js b/test/tests/menu-button_actions.js index da827c67d0..4be9345e6c 100644 --- a/test/tests/menu-button_actions.js +++ b/test/tests/menu-button_actions.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/menu-button_links.js b/test/tests/menu-button_links.js index 81e616c518..24da6720a3 100644 --- a/test/tests/menu-button_links.js +++ b/test/tests/menu-button_links.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/menubar_menubar-editor.js b/test/tests/menubar_menubar-editor.js index e736cf9ed8..f8cc9d7324 100644 --- a/test/tests/menubar_menubar-editor.js +++ b/test/tests/menubar_menubar-editor.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/menubar_menubar-navigation.js b/test/tests/menubar_menubar-navigation.js index 637926fa94..312d6cd492 100644 --- a/test/tests/menubar_menubar-navigation.js +++ b/test/tests/menubar_menubar-navigation.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/meter_meter.js b/test/tests/meter_meter.js index cc560fc609..8815c95906 100644 --- a/test/tests/meter_meter.js +++ b/test/tests/meter_meter.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By } = require('selenium-webdriver'); const assertAriaLabelledby = require('../util/assertAriaLabelledby'); diff --git a/test/tests/radio_radio-activedescendant.js b/test/tests/radio_radio-activedescendant.js index 05bde348be..5d3f0e32ea 100644 --- a/test/tests/radio_radio-activedescendant.js +++ b/test/tests/radio_radio-activedescendant.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/radio_radio.js b/test/tests/radio_radio.js index 8b353c8015..40b4a3d78e 100644 --- a/test/tests/radio_radio.js +++ b/test/tests/radio_radio.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/slider_multithumb.js b/test/tests/slider_multithumb.js index 71359a806e..068138437d 100644 --- a/test/tests/slider_multithumb.js +++ b/test/tests/slider_multithumb.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/slider_slider-1.js b/test/tests/slider_slider-1.js index d328987685..da4399249f 100644 --- a/test/tests/slider_slider-1.js +++ b/test/tests/slider_slider-1.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/slider_slider-2.js b/test/tests/slider_slider-2.js index b9849ca69a..9a4b770b0c 100644 --- a/test/tests/slider_slider-2.js +++ b/test/tests/slider_slider-2.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/spinbutton_datepicker.js b/test/tests/spinbutton_datepicker.js index 6f350a0a43..8e05767a60 100644 --- a/test/tests/spinbutton_datepicker.js +++ b/test/tests/spinbutton_datepicker.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const assertAttributeValues = require('../util/assertAttributeValues'); const assertAriaLabelledby = require('../util/assertAriaLabelledby'); diff --git a/test/tests/table_table.js b/test/tests/table_table.js index 300d9c1a3a..437d2c8479 100644 --- a/test/tests/table_table.js +++ b/test/tests/table_table.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const assertAriaDescribedby = require('../util/assertAriaDescribedby'); const assertAriaLabelExists = require('../util/assertAriaLabelExists'); diff --git a/test/tests/tabs_tabs-1.js b/test/tests/tabs_tabs-1.js index 85cd7b126b..2b972b3c51 100644 --- a/test/tests/tabs_tabs-1.js +++ b/test/tests/tabs_tabs-1.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/tabs_tabs-2.js b/test/tests/tabs_tabs-2.js index 0db668c9ca..b2706b3b78 100644 --- a/test/tests/tabs_tabs-2.js +++ b/test/tests/tabs_tabs-2.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/toolbar_toolbar.js b/test/tests/toolbar_toolbar.js index 2278f50dff..04e8cc3f9b 100644 --- a/test/tests/toolbar_toolbar.js +++ b/test/tests/toolbar_toolbar.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAriaLabelExists = require('../util/assertAriaLabelExists'); diff --git a/test/tests/treegrid_treegrid-1.js b/test/tests/treegrid_treegrid-1.js index 70c3f32cfa..4782468420 100644 --- a/test/tests/treegrid_treegrid-1.js +++ b/test/tests/treegrid_treegrid-1.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key, until } = require('selenium-webdriver'); const assertAriaRoles = require('../util/assertAriaRoles'); diff --git a/test/tests/treeview_treeview-1a.js b/test/tests/treeview_treeview-1a.js index 9a125df253..2bdda5a221 100644 --- a/test/tests/treeview_treeview-1a.js +++ b/test/tests/treeview_treeview-1a.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/treeview_treeview-1b.js b/test/tests/treeview_treeview-1b.js index 7a63271ad4..94d08e725f 100644 --- a/test/tests/treeview_treeview-1b.js +++ b/test/tests/treeview_treeview-1b.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/treeview_treeview-2a.js b/test/tests/treeview_treeview-2a.js index bc27e8f976..2f6d5d8b50 100644 --- a/test/tests/treeview_treeview-2a.js +++ b/test/tests/treeview_treeview-2a.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/tests/treeview_treeview-2b.js b/test/tests/treeview_treeview-2b.js index 7d7dd7d9ec..56976da658 100644 --- a/test/tests/treeview_treeview-2b.js +++ b/test/tests/treeview_treeview-2b.js @@ -1,5 +1,3 @@ -'use strict'; - const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); diff --git a/test/util/assertAriaActivedescendant.js b/test/util/assertAriaActivedescendant.js index 074b50b604..c281dd25b5 100644 --- a/test/util/assertAriaActivedescendant.js +++ b/test/util/assertAriaActivedescendant.js @@ -1,5 +1,3 @@ -'use strict'; - const { By } = require('selenium-webdriver'); const assert = require('assert'); diff --git a/test/util/assertAriaControls.js b/test/util/assertAriaControls.js index bba00de566..9518f9c630 100644 --- a/test/util/assertAriaControls.js +++ b/test/util/assertAriaControls.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); /** diff --git a/test/util/assertAriaDescribedby.js b/test/util/assertAriaDescribedby.js index 7e99a12e02..a40cd051a9 100644 --- a/test/util/assertAriaDescribedby.js +++ b/test/util/assertAriaDescribedby.js @@ -1,5 +1,3 @@ -'use strict'; - const { By } = require('selenium-webdriver'); const assert = require('assert'); diff --git a/test/util/assertAriaLabelExists.js b/test/util/assertAriaLabelExists.js index 1baf6460bb..ab855f20c9 100644 --- a/test/util/assertAriaLabelExists.js +++ b/test/util/assertAriaLabelExists.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); /** diff --git a/test/util/assertAriaLabelledby.js b/test/util/assertAriaLabelledby.js index 9564a5ad77..4b14ded6d3 100644 --- a/test/util/assertAriaLabelledby.js +++ b/test/util/assertAriaLabelledby.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); /** diff --git a/test/util/assertAriaRoles.js b/test/util/assertAriaRoles.js index d69a25208f..975e0ea92a 100644 --- a/test/util/assertAriaRoles.js +++ b/test/util/assertAriaRoles.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); /** diff --git a/test/util/assertAriaSelectedAndActivedescendant.js b/test/util/assertAriaSelectedAndActivedescendant.js index e5deb0c56b..e23ce410af 100644 --- a/test/util/assertAriaSelectedAndActivedescendant.js +++ b/test/util/assertAriaSelectedAndActivedescendant.js @@ -1,5 +1,3 @@ -'use strict'; - const { By } = require('selenium-webdriver'); const assert = require('assert'); diff --git a/test/util/assertAttributeDNE.js b/test/util/assertAttributeDNE.js index 28c04d5ddb..1cfb169982 100644 --- a/test/util/assertAttributeDNE.js +++ b/test/util/assertAttributeDNE.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); /** diff --git a/test/util/assertAttributeValues.js b/test/util/assertAttributeValues.js index 5bf3572c69..e10d6e31f9 100644 --- a/test/util/assertAttributeValues.js +++ b/test/util/assertAttributeValues.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); /** diff --git a/test/util/assertHasFocus.js b/test/util/assertHasFocus.js index 55472a246f..7ad48003ec 100644 --- a/test/util/assertHasFocus.js +++ b/test/util/assertHasFocus.js @@ -1,5 +1,3 @@ -'use strict'; - const { By, Key } = require('selenium-webdriver'); const assert = require('assert'); diff --git a/test/util/assertNoElements.js b/test/util/assertNoElements.js index c6968e597d..8c34e3b5f3 100644 --- a/test/util/assertNoElements.js +++ b/test/util/assertNoElements.js @@ -1,7 +1,3 @@ -/* eslint no-restricted-properties: 0 */ - -'use strict'; - const { By } = require('selenium-webdriver'); /** @@ -14,6 +10,7 @@ const { By } = require('selenium-webdriver'); * @returns {Promise} Resolves to array of elements */ module.exports = async function assertNoElements(t, selector, message) { + // eslint-disable-next-line no-restricted-properties const elements = await t.context.session.findElements(By.css(selector)); const errorMessage = message || 'Should return no results for CSS selector ' + selector; diff --git a/test/util/assertRovingTabindex.js b/test/util/assertRovingTabindex.js index f8acde8eb3..06c0b0ab9d 100644 --- a/test/util/assertRovingTabindex.js +++ b/test/util/assertRovingTabindex.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); /** diff --git a/test/util/assertTabOrder.js b/test/util/assertTabOrder.js index eadd1f96ed..317bdd62dc 100644 --- a/test/util/assertTabOrder.js +++ b/test/util/assertTabOrder.js @@ -1,5 +1,3 @@ -'use strict'; - const { By, Key } = require('selenium-webdriver'); const assert = require('assert'); diff --git a/test/util/force-serial.js b/test/util/force-serial.js index 892f78d5f2..a8006e18ce 100644 --- a/test/util/force-serial.js +++ b/test/util/force-serial.js @@ -1,4 +1,3 @@ -'use strict'; const net = require('net'); function bindPort(port) { diff --git a/test/util/get-json.js b/test/util/get-json.js index 4f2b227f19..22d643f9c9 100644 --- a/test/util/get-json.js +++ b/test/util/get-json.js @@ -1,5 +1,3 @@ -'use strict'; - const http = require('http'); module.exports = function getJSON(url) { diff --git a/test/util/queryElement.js b/test/util/queryElement.js index d529fa6310..9db1c43281 100644 --- a/test/util/queryElement.js +++ b/test/util/queryElement.js @@ -1,5 +1,3 @@ -'use strict'; - const { By } = require('selenium-webdriver'); /** diff --git a/test/util/queryElements.js b/test/util/queryElements.js index b82dc10503..1aef3bf86e 100644 --- a/test/util/queryElements.js +++ b/test/util/queryElements.js @@ -1,7 +1,3 @@ -/* eslint no-restricted-properties: 0 */ - -'use strict'; - const { By } = require('selenium-webdriver'); /** @@ -15,6 +11,7 @@ const { By } = require('selenium-webdriver'); */ module.exports = async function queryElements(t, selector, context) { context = context || t.context.session; + // eslint-disable-next-line no-restricted-properties const result = await context.findElements(By.css(selector)); if (result.length === 0) { t.fail(`Element query returned no results: ${selector}`); diff --git a/test/util/report.js b/test/util/report.js index 6a74fe8318..7d70387cef 100644 --- a/test/util/report.js +++ b/test/util/report.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -'use strict'; const cheerio = require('cheerio'); const path = require('path'); diff --git a/test/util/start-geckodriver.js b/test/util/start-geckodriver.js index 2e4a32a9fe..29313732c9 100644 --- a/test/util/start-geckodriver.js +++ b/test/util/start-geckodriver.js @@ -1,5 +1,3 @@ -'use strict'; - const assert = require('assert'); const { path: binaryPath } = require('geckodriver'); const { spawn } = require('child_process'); From c2ee25e88ac6a114c21a7aec699439588c08b4a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Oct 2020 21:25:16 -0700 Subject: [PATCH 33/34] Infrastructure: Bump eslint-config-prettier package from 6.12.0 to 6.14.0 (pull #1578) Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 6.12.0 to 6.14.0. - [Release notes](https://github.com/prettier/eslint-config-prettier/releases) - [Changelog](https://github.com/prettier/eslint-config-prettier/blob/master/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-config-prettier/compare/v6.12.0...v6.14.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa1620c250..a8ff904dbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2316,9 +2316,9 @@ } }, "eslint-config-prettier": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz", - "integrity": "sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.14.0.tgz", + "integrity": "sha512-DbVwh0qZhAC7CNDWcq8cBdK6FcVHiMTKmCypOPWeZkp9hJ8xYwTaWSa6bb6cjfi8KOeJy0e9a8Izxyx+O4+gCQ==", "dev": true, "requires": { "get-stdin": "^6.0.0" diff --git a/package.json b/package.json index d12296594d..efbc05cc06 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "cheerio": "^1.0.0-rc.2", "cspell": "^4.1.2", "eslint": "^7.11.0", - "eslint-config-prettier": "^6.12.0", + "eslint-config-prettier": "^6.14.0", "eslint-plugin-ava": "^11.0.0", "eslint-plugin-prettier": "^3.1.4", "geckodriver": "^1.20.0", From 19c745ab9dd5e3e77d36afff848877c6b06c0114 Mon Sep 17 00:00:00 2001 From: Sarah Higley Date: Thu, 22 Oct 2020 21:35:44 -0700 Subject: [PATCH 34/34] Accessible Name Guidance by Role Section: change marquee name from required to discretionary (pull #1572) --- aria-practices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aria-practices.html b/aria-practices.html index 9771a007dd..c14954bb04 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -4586,7 +4586,7 @@

        Accessible Name Guidance by Role marquee - Required + Discretionary Use aria-labelledby if a visible label is present, otherwise use aria-label.