Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions generate-act-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ const rulesToIgnore = [
"97a4e1", // Button has non-empty accessible name - not implemented
"cae760", // Iframe element has non-empty accessible name - not implemented
"e086e5", // Form field has non-empty accessible name - not implemented
"ffd0e9", // Heading has non-empty accessible name - not implemented
"m6b1q3", // Menuitem has non-empty accessible name - not implemented

// --- Not implemented - ARIA rules not yet fully supported ---
Expand All @@ -137,8 +136,6 @@ const rulesToIgnore = [

// --- Not implemented - page-level and structural rules ---
"2779a5", // HTML page has non-empty title - not implemented in ACT test format
"b5c3f8", // HTML page has lang attribute - not implemented in ACT test format
"bf051a", // HTML page lang attribute has valid language tag - not implemented in ACT test format
"off6ek", // HTML element language subtag matches language - not implemented
"ucwvc8", // HTML page language subtag matches default language - not implemented
"c4a8a4", // HTML page title is descriptive - not implemented, requires human judgment
Expand All @@ -160,7 +157,6 @@ const rulesToIgnore = [
"efbfc7", // Text content that changes automatically can be paused, stopped or hidden - not implemented

// --- Unknown or deprecated ACT rules ---
"3ea0c8", // Unknown ACT rule - not in current testcases
"e6952f", // Unknown ACT rule - not in current testcases
];

Expand Down
8 changes: 4 additions & 4 deletions rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@
"ACT Rules": "[3ea0c8](https://act-rules.github.io/rules/3ea0c8)"
},
{
"implemented": "",
"implemented": "",
"id": "duplicate-id",
"url": "https://dequeuniversity.com/rules/axe/4.11/duplicate-id?application=RuleDescription",
"Description": "Ensures every id attribute value is unique",
Expand Down Expand Up @@ -323,7 +323,7 @@
"ACT Rules": "[cae760](https://act-rules.github.io/rules/cae760)"
},
{
"implemented": "",
"implemented": "",
"id": "html-has-lang",
"url": "https://dequeuniversity.com/rules/axe/4.11/html-has-lang?application=RuleDescription",
"Description": "Ensures every HTML document has a lang attribute",
Expand All @@ -333,7 +333,7 @@
"ACT Rules": "[b5c3f8](https://act-rules.github.io/rules/b5c3f8)"
},
{
"implemented": "",
"implemented": "",
"id": "html-lang-valid",
"url": "https://dequeuniversity.com/rules/axe/4.11/html-lang-valid?application=RuleDescription",
"Description": "Ensures the lang attribute of the <html> element has a valid value",
Expand Down Expand Up @@ -653,7 +653,7 @@
"ACT Rules": ""
},
{
"implemented": "",
"implemented": "",
"id": "empty-heading",
"url": "https://dequeuniversity.com/rules/axe/4.11/empty-heading?application=RuleDescription",
"Description": "Ensures headings have discernible text",
Expand Down
24 changes: 16 additions & 8 deletions src/rules/aria-allowed-attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,25 +99,33 @@ const implicitRoles: Record<string, string | undefined> = {
function getInputImplicitRole(element: Element): string | undefined {
const type = (element.getAttribute("type") || "text").toLowerCase();
switch (type) {
case "checkbox":
case "checkbox": {
return "checkbox";
case "radio":
}
case "radio": {
return "radio";
case "range":
}
case "range": {
return "slider";
case "number":
}
case "number": {
return "spinbutton";
case "search":
}
case "search": {
return "searchbox";
}
case "button":
case "image":
case "reset":
case "submit":
case "submit": {
return "button";
case "hidden":
}
case "hidden": {
return undefined;
default:
}
default: {
return "textbox";
}
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/rules/html-lang-valid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ const text = "The lang attribute of the <html> element must have a valid value";
const url = `https://dequeuniversity.com/rules/axe/4.11/${id}`;

function langIsValid(locale: string): boolean {
// Extract the primary language subtag (before any hyphen).
// BCP 47 requires only the primary subtag to be a valid ISO 639 code;
// additional subtags (region, variant, etc.) don't affect validity for
// this rule.
const primarySubtag = locale.split("-")[0];
if (!primarySubtag) return false;

try {
const foundLocales = Intl.DisplayNames.supportedLocalesOf([locale], {
const foundLocales = Intl.DisplayNames.supportedLocalesOf([primarySubtag], {
localeMatcher: "lookup",
});
if (foundLocales.length !== 1) return false;
return foundLocales[0].toLowerCase() === locale.toLowerCase();
return foundLocales[0].toLowerCase() === primarySubtag.toLowerCase();
} catch {
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions src/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import list from "./rules/list";
import dlitem from "./rules/dlitem";
import nestedInteractive from "./rules/nested-interactive";
import validLang from "./rules/valid-lang";
import htmlHasLang from "./rules/html-has-lang";
import htmlLangValid from "./rules/html-lang-valid";
import htmlXmlLangMismatch from "./rules/html-xml-lang-mismatch";
import colorContrast from "./rules/color-contrast";
Expand Down Expand Up @@ -156,6 +157,7 @@ export const allRules: Rule[] = [
frameTitle,
frameTitleUnique,
headingOrder,
htmlHasLang,
htmlLangValid,
htmlXmlLangMismatch,
identicalLinksSamePurpose,
Expand Down
23 changes: 23 additions & 0 deletions tests/act/tests/b5c3f8/0fac26928e2bf6b7db6c7f46a1e0ab50aaa8a7c1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[b5c3f8]HTML page has lang attribute", function () {
it("Passed Example 1 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/b5c3f8/0fac26928e2bf6b7db6c7f46a1e0ab50aaa8a7c1.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="en">
<body>
The quick brown fox jumps over the lazy dog.
</body>
</html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-has-lang"];
const relevant = results.filter(r => expectedUrls.includes(r.url));
expect(relevant).to.be.empty;
});
});
23 changes: 23 additions & 0 deletions tests/act/tests/b5c3f8/473352935acf2463b14dbd8e38073e913eeb5c08.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[b5c3f8]HTML page has lang attribute", function () {
it("Failed Example 1 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/b5c3f8/473352935acf2463b14dbd8e38073e913eeb5c08.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html>
<body>
The quick brown fox jumps over the lazy dog.
</body>
</html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-has-lang"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
23 changes: 23 additions & 0 deletions tests/act/tests/b5c3f8/4ea0280617a1b71dcc327356484f8767919b0f40.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[b5c3f8]HTML page has lang attribute", function () {
it("Failed Example 3 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/b5c3f8/4ea0280617a1b71dcc327356484f8767919b0f40.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang=" ">
<body>
The quick brown fox jumps over the lazy dog.
</body>
</html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-has-lang"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
23 changes: 23 additions & 0 deletions tests/act/tests/b5c3f8/4f94c3e26f43701d91db403fe26cd8894bdc8ccf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[b5c3f8]HTML page has lang attribute", function () {
it("Failed Example 4 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/b5c3f8/4f94c3e26f43701d91db403fe26cd8894bdc8ccf.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html xml:lang="en">
<body>
The quick brown fox jumps over the lazy dog.
</body>
</html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-has-lang"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
23 changes: 23 additions & 0 deletions tests/act/tests/b5c3f8/98681b2a7949e49b2da1b353f70e688528fe7ddc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[b5c3f8]HTML page has lang attribute", function () {
it("Failed Example 2 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/b5c3f8/98681b2a7949e49b2da1b353f70e688528fe7ddc.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="">
<body>
The quick brown fox jumps over the lazy dog.
</body>
</html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-has-lang"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
23 changes: 23 additions & 0 deletions tests/act/tests/bf051a/0f73e7179e17f050380f0ea350d2551611820fd5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[bf051a]HTML page `lang` attribute has valid language tag", function () {
it("Failed Example 3 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/bf051a/0f73e7179e17f050380f0ea350d2551611820fd5.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="eng">
<body>
<p lang="en">I love ACT rules!</p>
</body>
</html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-lang-valid"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
19 changes: 19 additions & 0 deletions tests/act/tests/bf051a/5c998eef8cb13a8f577dade1a3b9fe591bc69204.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[bf051a]HTML page `lang` attribute has valid language tag", function () {
it("Failed Example 2 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/bf051a/5c998eef8cb13a8f577dade1a3b9fe591bc69204.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="#1"></html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-lang-valid"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
19 changes: 19 additions & 0 deletions tests/act/tests/bf051a/7d8c4fd028c504d10c4e5e9bd7183c139549e1a1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[bf051a]HTML page `lang` attribute has valid language tag", function () {
it("Passed Example 1 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/bf051a/7d8c4fd028c504d10c4e5e9bd7183c139549e1a1.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="FR"></html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-lang-valid"];
const relevant = results.filter(r => expectedUrls.includes(r.url));
expect(relevant).to.be.empty;
});
});
19 changes: 19 additions & 0 deletions tests/act/tests/bf051a/a49f11c86ad81c4d42700dfca58a7eeec377f02e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[bf051a]HTML page `lang` attribute has valid language tag", function () {
it("Passed Example 2 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/bf051a/a49f11c86ad81c4d42700dfca58a7eeec377f02e.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="en-US-GB"></html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-lang-valid"];
const relevant = results.filter(r => expectedUrls.includes(r.url));
expect(relevant).to.be.empty;
});
});
23 changes: 23 additions & 0 deletions tests/act/tests/bf051a/b64d767d873269ff00966630e34ab198fc24368f.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[bf051a]HTML page `lang` attribute has valid language tag", function () {
it("Failed Example 4 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/bf051a/b64d767d873269ff00966630e34ab198fc24368f.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="i-lux">
<body>
<p lang="lb">Lëtzebuerg ass e Land an Europa.</p>
</body>
</html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-lang-valid"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
19 changes: 19 additions & 0 deletions tests/act/tests/bf051a/b7a35f8080e756776877bca013a910dafde8ef73.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from "@open-wc/testing";
import { scan } from "../../../../src/scanner";

const parser = new DOMParser();

describe("[bf051a]HTML page `lang` attribute has valid language tag", function () {
it("Failed Example 1 (https://www.w3.org/WAI/content-assets/wcag-act-rules/testcases/bf051a/b7a35f8080e756776877bca013a910dafde8ef73.html)", async () => {
const document = parser.parseFromString(`<!DOCTYPE html>
<html lang="em-US"></html>`, 'text/html');

const results = (await scan(document.body)).map(({ text, url }) => {
return { text, url };
});

expect(results).to.not.be.empty;
const expectedUrls = ["https://dequeuniversity.com/rules/axe/4.11/html-lang-valid"];
expect(results.some(r => expectedUrls.includes(r.url))).to.be.true;
});
});
Loading