Skip to content

Commit

Permalink
aria-required-attr (#37)
Browse files Browse the repository at this point in the history
* aria-required-attr

* try fixing tooltip tests

* use summary reporter

* publish test results

* increase permissions for action
  • Loading branch information
koddsson authored Jun 19, 2024
1 parent f093731 commit fae85ac
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 4 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jobs:
runs-on: macos-latest
permissions:
pull-requests: write
contents: read
checks: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -27,3 +30,8 @@ jobs:
minimum-coverage: 90
artifact-name: code-coverage-report
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure() # always run even if the previous step fails
with:
report_paths: './test-results.xml'
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
|| aria-input-field-name | https://dequeuniversity.com/rules/axe/4.4/aria-input-field-name?application=RuleDescription | Ensures every ARIA input field has an accessible name | Moderate, Serious | cat.aria, wcag2a, wcag412, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) |
|| aria-meter-name | https://dequeuniversity.com/rules/axe/4.4/aria-meter-name?application=RuleDescription | Ensures every ARIA meter node has an accessible name | Serious | cat.aria, wcag2a, wcag111 | failure, needs review | |
|| aria-progressbar-name | https://dequeuniversity.com/rules/axe/4.4/aria-progressbar-name?application=RuleDescription | Ensures every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111 | failure, needs review | |
| | aria-required-attr | https://dequeuniversity.com/rules/axe/4.4/aria-required-attr?application=RuleDescription | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412 | failure | [4e8ab6](https://act-rules.github.io/rules/4e8ab6) |
| | aria-required-attr | https://dequeuniversity.com/rules/axe/4.4/aria-required-attr?application=RuleDescription | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412 | failure | [4e8ab6](https://act-rules.github.io/rules/4e8ab6) |
|| aria-required-children | https://dequeuniversity.com/rules/axe/4.4/aria-required-children?application=RuleDescription | Ensures elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131 | failure, needs review | [bc4a75](https://act-rules.github.io/rules/bc4a75) |
|| aria-required-parent | https://dequeuniversity.com/rules/axe/4.4/aria-required-parent?application=RuleDescription | Ensures elements with an ARIA role that require parent roles are contained by them | Critical | cat.aria, wcag2a, wcag131 | failure | [ff89c9](https://act-rules.github.io/rules/ff89c9) |
|| aria-roledescription | https://dequeuniversity.com/rules/axe/4.4/aria-roledescription?application=RuleDescription | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412 | failure, needs review | |
Expand Down
34 changes: 34 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@web/dev-server": "^0.4.5",
"@web/dev-server-esbuild": "^1.0.2",
"@web/test-runner": "^0.18.2",
"@web/test-runner-junit-reporter": "^0.7.1",
"@web/test-runner-playwright": "^0.11.0",
"eslint": "^8.25.0",
"eslint-plugin-github": "^4.4.0",
Expand Down
71 changes: 71 additions & 0 deletions src/rules/aria-required-attr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { AccessibilityError } from "../scanner";
import { querySelectorAll } from "../utils";

// TODO: This list is incomplete!
type Role =
| "checkbox"
| "combobox"
| "heading"
| "menuitemcheckbox"
| "menuitemradio"
| "meter"
| "radio"
| "scrollbar"
| "seperator"
| "slider"
| "switch";

// TODO: This list is incomplete!
type AriaAttribute =
| "aria-checked"
| "aria-expanded"
| "aria-level"
| "aria-checked"
| "aria-valuenow"
| "aria-controls";

/**
* Required States and Properties:
*
* @see https://w3c.github.io/aria/#authorErrorDefaultValuesTable
*/
const roleToRequiredStatesAndPropertiesMaps: Record<Role, AriaAttribute[]> = {
checkbox: ["aria-checked"],
combobox: ["aria-expanded"],
heading: ["aria-level"],
menuitemcheckbox: ["aria-checked"],
menuitemradio: ["aria-checked"],
meter: ["aria-valuenow"],
radio: ["aria-checked"],
scrollbar: ["aria-controls", "aria-valuenow"],
// If focusable
seperator: ["aria-valuenow"],
slider: ["aria-valuenow"],
switch: ["aria-checked"],
};

const id = "aria-required-attr";
const text = "Required ARIA attributes must be provided";
const url = `https://dequeuniversity.com/rules/axe/4.4/${id}?application=RuleDescription`;

export function ariaRequiredAttr(el: Element): AccessibilityError[] {
const errors = [];

const selector = Object.entries(roleToRequiredStatesAndPropertiesMaps)
.map(([role, attributes]) => {
return `[role=${role}]:not(${attributes.map((attr) => `[${attr}]`).join("")})`;
})
.join(",");

const elements = querySelectorAll(selector, el);
if (el.matches(selector)) elements.push(el);

for (const element of elements) {
errors.push({
element,
text,
url,
});
}
return errors;
}
54 changes: 54 additions & 0 deletions tests/aria-required-attr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { fixture, expect } from "@open-wc/testing";
import { Scanner } from "../src/scanner";
import ariaValidAttr from "../src/rules/aria-valid-attr";

const scanner = new Scanner([ariaValidAttr]);

const passes = [
'<div id="target" role="switch" tabindex="1" aria-checked="false">',
'<div id="target"></div>',
'<input id="target" type="range" role="slider">',
'<input id="target" type="checkbox" role="switch">',
'<div id="target" role="separator"></div>',
'<div id="target" role="combobox" aria-expanded="false"></div>',
'<div id="target" role="combobox" aria-expanded="true" aria-controls="test"></div>',
];
const violations = [
'<div id="target" role="switch" tabindex="1">',
'<div id="target" role="switch" tabindex="1" aria-checked>',
'<div id="target" role="switch" tabindex="1" aria-checked="">',
'<div id="target" role="separator" tabindex="0"></div>',
'<div id="target" role="combobox" aria-expanded="invalid-value"></div>',
'<div id="target" role="combobox" aria-expanded="true" aria-owns="ownedchild"></div>',
'<div id="target" role="combobox"></div>',
'<div id="target" role="combobox" aria-expanded="true"></div>',
];

describe("aria-required-attr", async function () {
for (const markup of passes) {
const el = await fixture(markup);
it(el.outerHTML, async () => {
const results = (await scanner.scan(el)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.be.empty;
});
}

for (const markup of violations) {
const el = await fixture(markup);
it(el.outerHTML, async () => {
const results = (await scanner.scan(el)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.eql([
{
text: "ARIA attributes must conform to valid names",
url: "https://dequeuniversity.com/rules/axe/4.4/aria-valid-attr",
},
]);
});
}
});
4 changes: 2 additions & 2 deletions tests/aria-tooltip-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const violations = [

describe("aria-tooltip-name", async function () {
for (const markup of passes) {
const el = await fixture(html`${markup}`);
const el = await fixture(markup);
it(el.outerHTML, async () => {
const results = (await scanner.scan(el)).map(({ text, url }) => {
return { text, url };
Expand All @@ -41,7 +41,7 @@ describe("aria-tooltip-name", async function () {
}

for await (const markup of violations) {
const el = await fixture(html`${markup}`);
const el = await fixture(markup);
it(el.outerHTML, async () => {
const results = (await scanner.scan(el)).map(({ text, url }) => {
return { text, url };
Expand Down
18 changes: 17 additions & 1 deletion web-test-runner.config.mjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
// eslint-disable-next-line foo
import { env } from "node:process";

import { summaryReporter } from "@web/test-runner";
import { esbuildPlugin } from "@web/dev-server-esbuild";
import { playwrightLauncher } from "@web/test-runner-playwright";
import { junitReporter } from "@web/test-runner-junit-reporter";

const browsers = [playwrightLauncher({ product: "chromium" })];

if (process.env.CI) {
if (env.CI) {
browsers.push(
playwrightLauncher({ product: "firefox" }),
playwrightLauncher({ product: "webkit" }),
);
}

const reporters = [
summaryReporter(),
env.CI
? junitReporter({
outputPath: "./test-results.xml",
reportLogs: true,
})
: null,
];

export default {
nodeResolve: true,
coverage: true,
files: ["tests/**/*.ts", "tests/**/*.js"],
plugins: [esbuildPlugin({ ts: true, target: "esnext" })],
browsers,
reporters,
filterBrowserLogs(log) {
if (
typeof log.args[0] === "string" &&
Expand Down

0 comments on commit fae85ac

Please sign in to comment.