From 2ecfea71a164bd039d52ea76ecd0c6a8814e2405 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Fri, 24 Aug 2018 14:25:16 +0200 Subject: [PATCH] feat: Break up duplicate-id rule for ARIA+labels and active elements --- doc/rule-descriptions.md | 4 +- .../duplicate-id-active.json} | 5 +- .../{shared => parsing}/duplicate-id-after.js | 0 lib/checks/parsing/duplicate-id-aria.json | 12 +++ .../{shared => parsing}/duplicate-id.js | 11 +-- .../{shared => parsing}/duplicate-id.json | 3 - lib/commons/aria/is-accessible-ref.js | 4 +- lib/rules/duplicate-id-active-matches.js | 8 ++ lib/rules/duplicate-id-active.json | 20 +++++ lib/rules/duplicate-id-aria-matches.js | 1 + lib/rules/duplicate-id-aria.json | 20 +++++ lib/rules/duplicate-id-misc-matches.js | 11 +++ lib/rules/duplicate-id.json | 4 +- .../checks/{shared => parser}/duplicate-id.js | 46 ----------- test/commons/aria/is-accessible-ref.js | 2 +- .../duplicate-id-active.html | 19 +++++ .../duplicate-id-active.json | 6 ++ .../duplicate-id-aria/duplicate-id-aria.html | 19 +++++ .../duplicate-id-aria/duplicate-id-aria.json | 6 ++ .../rules/duplicate-id/duplicate-id.html | 18 ++++- .../rules/duplicate-id/duplicate-id.json | 4 +- .../duplicate-id-active-matches.js | 79 +++++++++++++++++++ .../duplicate-id-aria-matches.2.js | 79 +++++++++++++++++++ .../rule-matches/duplicate-id-misc-matches.js | 79 +++++++++++++++++++ 24 files changed, 388 insertions(+), 72 deletions(-) rename lib/checks/{shared/duplicate-id-accessible.json => parsing/duplicate-id-active.json} (79%) rename lib/checks/{shared => parsing}/duplicate-id-after.js (100%) create mode 100644 lib/checks/parsing/duplicate-id-aria.json rename lib/checks/{shared => parsing}/duplicate-id.js (70%) rename lib/checks/{shared => parsing}/duplicate-id.json (87%) create mode 100644 lib/rules/duplicate-id-active-matches.js create mode 100644 lib/rules/duplicate-id-active.json create mode 100644 lib/rules/duplicate-id-aria-matches.js create mode 100644 lib/rules/duplicate-id-aria.json create mode 100644 lib/rules/duplicate-id-misc-matches.js rename test/checks/{shared => parser}/duplicate-id.js (73%) create mode 100644 test/integration/rules/duplicate-id-active/duplicate-id-active.html create mode 100644 test/integration/rules/duplicate-id-active/duplicate-id-active.json create mode 100644 test/integration/rules/duplicate-id-aria/duplicate-id-aria.html create mode 100644 test/integration/rules/duplicate-id-aria/duplicate-id-aria.json create mode 100644 test/rule-matches/duplicate-id-active-matches.js create mode 100644 test/rule-matches/duplicate-id-aria-matches.2.js create mode 100644 test/rule-matches/duplicate-id-misc-matches.js diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 8ce525d6bc..c3afebc390 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -23,7 +23,9 @@ | definition-list | Ensures <dl> elements are structured correctly | Serious | cat.structure, wcag2a, wcag131 | true | | dlitem | Ensures <dt> and <dd> elements are contained by a <dl> | Serious | cat.structure, wcag2a, wcag131 | true | | document-title | Ensures each HTML document contains a non-empty <title> element | Serious | cat.text-alternatives, wcag2a, wcag242 | true | -| duplicate-id | Ensures every id attribute value is unique | Minor, Serious | cat.parsing, wcag2a, wcag411 | true | +| duplicate-id-active | Ensures every id attribute value is unique | Serious | cat.parsing, wcag2a, wcag411 | true | +| duplicate-id-aria | Ensures every id attribute value used in ARIA and in labels is unique | Critical | cat.parsing, wcag2a, wcag411 | true | +| duplicate-id | Ensures every id attribute value is unique | Minor | cat.parsing, wcag2a, wcag411 | true | | empty-heading | Ensures headings have discernible text | Minor | cat.name-role-value, best-practice | true | | focus-order-semantics | Ensures elements in the focus order have an appropriate role | Minor | cat.keyboard, best-practice, experimental | true | | frame-tested | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, review-item | true | diff --git a/lib/checks/shared/duplicate-id-accessible.json b/lib/checks/parsing/duplicate-id-active.json similarity index 79% rename from lib/checks/shared/duplicate-id-accessible.json rename to lib/checks/parsing/duplicate-id-active.json index 4b8163e42b..5ef2f179d3 100644 --- a/lib/checks/shared/duplicate-id-accessible.json +++ b/lib/checks/parsing/duplicate-id-active.json @@ -1,10 +1,7 @@ { - "id": "duplicate-id-accessible", + "id": "duplicate-id-active", "evaluate": "duplicate-id.js", "after": "duplicate-id-after.js", - "options": { - "accReferred": true - }, "metadata": { "impact": "serious", "messages": { diff --git a/lib/checks/shared/duplicate-id-after.js b/lib/checks/parsing/duplicate-id-after.js similarity index 100% rename from lib/checks/shared/duplicate-id-after.js rename to lib/checks/parsing/duplicate-id-after.js diff --git a/lib/checks/parsing/duplicate-id-aria.json b/lib/checks/parsing/duplicate-id-aria.json new file mode 100644 index 0000000000..26de80fedb --- /dev/null +++ b/lib/checks/parsing/duplicate-id-aria.json @@ -0,0 +1,12 @@ +{ + "id": "duplicate-id-aria", + "evaluate": "duplicate-id.js", + "after": "duplicate-id-after.js", + "metadata": { + "impact": "critical", + "messages": { + "pass": "Document has no elements that share the same id attribute", + "fail": "Document has multiple elements with the same id attribute: {{=it.data}}" + } + } +} diff --git a/lib/checks/shared/duplicate-id.js b/lib/checks/parsing/duplicate-id.js similarity index 70% rename from lib/checks/shared/duplicate-id.js rename to lib/checks/parsing/duplicate-id.js index d504efe79d..422fd52a29 100644 --- a/lib/checks/shared/duplicate-id.js +++ b/lib/checks/parsing/duplicate-id.js @@ -1,18 +1,13 @@ -const { aria, dom, utils } = axe.commons; +const { dom, utils } = axe.commons; const id = node.getAttribute('id').trim(); -const { accReferred } = options || {}; +const root = dom.getRootNode(node); // Since empty ID's are not meaningful and are ignored by Edge, we'll // let those pass. -if ( - !id || - (typeof accReferred === 'boolean' && - accReferred === !aria.isAccessibleRef(node)) -) { +if (!id) { return true; } -const root = dom.getRootNode(node); const matchingNodes = Array.from( root.querySelectorAll(`[id="${utils.escapeSelector(id)}"]`) ).filter(foundNode => foundNode !== node); diff --git a/lib/checks/shared/duplicate-id.json b/lib/checks/parsing/duplicate-id.json similarity index 87% rename from lib/checks/shared/duplicate-id.json rename to lib/checks/parsing/duplicate-id.json index 201efcc542..e611b041a6 100644 --- a/lib/checks/shared/duplicate-id.json +++ b/lib/checks/parsing/duplicate-id.json @@ -2,9 +2,6 @@ "id": "duplicate-id", "evaluate": "duplicate-id.js", "after": "duplicate-id-after.js", - "options": { - "accReferred": false - }, "metadata": { "impact": "minor", "messages": { diff --git a/lib/commons/aria/is-accessible-ref.js b/lib/commons/aria/is-accessible-ref.js index e92aceac09..f743fd798a 100644 --- a/lib/commons/aria/is-accessible-ref.js +++ b/lib/commons/aria/is-accessible-ref.js @@ -3,8 +3,8 @@ function findDomNode(node, functor) { if (functor(node)) { return node; } - for (const child of node.children) { - const out = findDomNode(child, functor); + for (let i = 0; i < node.children.length; i++) { + const out = findDomNode(node.children[i], functor); if (out) { return out; } diff --git a/lib/rules/duplicate-id-active-matches.js b/lib/rules/duplicate-id-active-matches.js new file mode 100644 index 0000000000..1a0e025283 --- /dev/null +++ b/lib/rules/duplicate-id-active-matches.js @@ -0,0 +1,8 @@ +const { dom, aria } = axe.commons; +const id = node.getAttribute('id').trim(); +const idSelector = `*[id="${axe.utils.escapeSelector(id)}"]`; +const idMatchingElms = Array.from( + dom.getRootNode(node).querySelectorAll(idSelector) +); + +return idMatchingElms.some(dom.isFocusable) && !aria.isAccessibleRef(node); diff --git a/lib/rules/duplicate-id-active.json b/lib/rules/duplicate-id-active.json new file mode 100644 index 0000000000..94daba5328 --- /dev/null +++ b/lib/rules/duplicate-id-active.json @@ -0,0 +1,20 @@ +{ + "id": "duplicate-id-active", + "selector": "[id]", + "matches": "duplicate-id-active-matches.js", + "excludeHidden": false, + "tags": [ + "cat.parsing", + "wcag2a", + "wcag411" + ], + "metadata": { + "description": "Ensures every id attribute value is unique", + "help": "IDs of active elements must be unique" + }, + "all": [ + "duplicate-id-active" + ], + "any": [], + "none": [] +} diff --git a/lib/rules/duplicate-id-aria-matches.js b/lib/rules/duplicate-id-aria-matches.js new file mode 100644 index 0000000000..a91461fc34 --- /dev/null +++ b/lib/rules/duplicate-id-aria-matches.js @@ -0,0 +1 @@ +return axe.commons.aria.isAccessibleRef(node); diff --git a/lib/rules/duplicate-id-aria.json b/lib/rules/duplicate-id-aria.json new file mode 100644 index 0000000000..e9e655e0f1 --- /dev/null +++ b/lib/rules/duplicate-id-aria.json @@ -0,0 +1,20 @@ +{ + "id": "duplicate-id-aria", + "selector": "[id]", + "matches": "duplicate-id-aria-matches.js", + "excludeHidden": false, + "tags": [ + "cat.parsing", + "wcag2a", + "wcag411" + ], + "metadata": { + "description": "Ensures every id attribute value used in ARIA and in labels is unique", + "help": "IDs used in ARIA and labels must be unique" + }, + "all": [ + "duplicate-id-aria" + ], + "any": [], + "none": [] +} diff --git a/lib/rules/duplicate-id-misc-matches.js b/lib/rules/duplicate-id-misc-matches.js new file mode 100644 index 0000000000..3825053977 --- /dev/null +++ b/lib/rules/duplicate-id-misc-matches.js @@ -0,0 +1,11 @@ +const { dom, aria } = axe.commons; +const id = node.getAttribute('id').trim(); +const idSelector = `*[id="${axe.utils.escapeSelector(id)}"]`; +const idMatchingElms = Array.from( + dom.getRootNode(node).querySelectorAll(idSelector) +); + +return ( + idMatchingElms.every(elm => !dom.isFocusable(elm)) && + !aria.isAccessibleRef(node) +); diff --git a/lib/rules/duplicate-id.json b/lib/rules/duplicate-id.json index 4a103db048..8e2b4b5707 100644 --- a/lib/rules/duplicate-id.json +++ b/lib/rules/duplicate-id.json @@ -1,6 +1,7 @@ { "id": "duplicate-id", "selector": "[id]", + "matches": "duplicate-id-misc-matches.js", "excludeHidden": false, "tags": [ "cat.parsing", @@ -12,8 +13,7 @@ "help": "id attribute value must be unique" }, "all": [ - "duplicate-id", - "duplicate-id-accessible" + "duplicate-id" ], "any": [], "none": [] diff --git a/test/checks/shared/duplicate-id.js b/test/checks/parser/duplicate-id.js similarity index 73% rename from test/checks/shared/duplicate-id.js rename to test/checks/parser/duplicate-id.js index 9e745ef85e..71b5216db8 100644 --- a/test/checks/shared/duplicate-id.js +++ b/test/checks/parser/duplicate-id.js @@ -116,50 +116,4 @@ describe('duplicate-id', function() { assert.deepEqual(checkContext._relatedNodes, [node.querySelector('p')]); } ); - - describe('options.accReferred', function() { - it('ignores unreffed elements with accReferred: true', function() { - fixture.innerHTML = '
'; - var node = fixture.querySelector('div[id="foo"]'); - assert.isTrue( - checks['duplicate-id'].evaluate.call(checkContext, node, { - accReferred: true - }) - ); - }); - - it('tests reffed elements with accReferred: true', function() { - fixture.innerHTML = - '
' + - '
'; - var node = fixture.querySelector('div[id="foo"]'); - assert.isFalse( - checks['duplicate-id'].evaluate.call(checkContext, node, { - accReferred: true - }) - ); - }); - - it('ignores reffed elements with accReferred: false', function() { - fixture.innerHTML = - '
' + - '
'; - var node = fixture.querySelector('div[id="foo"]'); - assert.isTrue( - checks['duplicate-id'].evaluate.call(checkContext, node, { - accReferred: false - }) - ); - }); - - it('tests unreffed elements with accReferred: false', function() { - fixture.innerHTML = '
'; - var node = fixture.querySelector('div[id="foo"]'); - assert.isFalse( - checks['duplicate-id'].evaluate.call(checkContext, node, { - accReferred: false - }) - ); - }); - }); }); diff --git a/test/commons/aria/is-accessible-ref.js b/test/commons/aria/is-accessible-ref.js index 177c5352fa..4d926cf710 100644 --- a/test/commons/aria/is-accessible-ref.js +++ b/test/commons/aria/is-accessible-ref.js @@ -4,7 +4,7 @@ describe('aria.isAccessibleRef', function() { var __atrs; var fixture = document.getElementById('fixture'); var isAccessibleRef = axe.commons.aria.isAccessibleRef; - var shadowSupport = axe.testUtils.shadowSupport; + var shadowSupport = axe.testUtils.shadowSupport.v1; function setLookup(attrs) { axe.commons.aria.lookupTable.attributes = attrs; diff --git a/test/integration/rules/duplicate-id-active/duplicate-id-active.html b/test/integration/rules/duplicate-id-active/duplicate-id-active.html new file mode 100644 index 0000000000..f82aa81633 --- /dev/null +++ b/test/integration/rules/duplicate-id-active/duplicate-id-active.html @@ -0,0 +1,19 @@ +

This is my first paragraph with this ID.

+
+

This is my second paragraph with this ID.

+
+ + + + + + + +
+ + + +
+ + + \ No newline at end of file diff --git a/test/integration/rules/duplicate-id-active/duplicate-id-active.json b/test/integration/rules/duplicate-id-active/duplicate-id-active.json new file mode 100644 index 0000000000..d9ef605f83 --- /dev/null +++ b/test/integration/rules/duplicate-id-active/duplicate-id-active.json @@ -0,0 +1,6 @@ +{ + "description": "duplicate-id-active test", + "rule": "duplicate-id-active", + "violations": [[".fail1"]], + "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"]] +} diff --git a/test/integration/rules/duplicate-id-aria/duplicate-id-aria.html b/test/integration/rules/duplicate-id-aria/duplicate-id-aria.html new file mode 100644 index 0000000000..7aece2bec6 --- /dev/null +++ b/test/integration/rules/duplicate-id-aria/duplicate-id-aria.html @@ -0,0 +1,19 @@ +

This is my first paragraph with this ID.

+
+

This is my second paragraph with this ID.

+
+ + + + +
+ + + + + +
+ + + + \ No newline at end of file diff --git a/test/integration/rules/duplicate-id-aria/duplicate-id-aria.json b/test/integration/rules/duplicate-id-aria/duplicate-id-aria.json new file mode 100644 index 0000000000..9d2a7d4737 --- /dev/null +++ b/test/integration/rules/duplicate-id-aria/duplicate-id-aria.json @@ -0,0 +1,6 @@ +{ + "description": "duplicate-id-aria test", + "rule": "duplicate-id-aria", + "violations": [[".fail1"]], + "passes": [["#pass1"], ["#pass2"]] +} diff --git a/test/integration/rules/duplicate-id/duplicate-id.html b/test/integration/rules/duplicate-id/duplicate-id.html index 5260c063b8..0c9fb0fd16 100644 --- a/test/integration/rules/duplicate-id/duplicate-id.html +++ b/test/integration/rules/duplicate-id/duplicate-id.html @@ -1,5 +1,17 @@ -

This is my first paragraph with this ID.

+

This is my first paragraph with this ID.

-

This is my second paragraph with this ID.

+

This is my second paragraph with this ID.

-

This is my only paragraph with this ID.

+ + + + +
+ + + +
+ + + + \ No newline at end of file diff --git a/test/integration/rules/duplicate-id/duplicate-id.json b/test/integration/rules/duplicate-id/duplicate-id.json index baa508d0e1..983ab00e75 100644 --- a/test/integration/rules/duplicate-id/duplicate-id.json +++ b/test/integration/rules/duplicate-id/duplicate-id.json @@ -1,6 +1,6 @@ { "description": "duplicate-id test", "rule": "duplicate-id", - "violations": [["#fixture > p:nth-child(1)"]], - "passes": [["#fixture"], ["#single"]] + "violations": [[".fail1"]], + "passes": [["#fixture"], ["#pass1"], ["#pass2"], ["#pass3"]] } diff --git a/test/rule-matches/duplicate-id-active-matches.js b/test/rule-matches/duplicate-id-active-matches.js new file mode 100644 index 0000000000..8918934eee --- /dev/null +++ b/test/rule-matches/duplicate-id-active-matches.js @@ -0,0 +1,79 @@ +describe('duplicate-id-mismatches', function() { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var fixtureSetup = axe.testUtils.fixtureSetup; + var rule; + + beforeEach(function() { + rule = axe._audit.rules.find(function(rule) { + return rule.id === 'duplicate-id-active'; + }); + }); + + afterEach(function() { + fixture.innerHTML = ''; + }); + + it('is a function', function() { + assert.isFunction(rule.matches); + }); + + it('returns false if the ID is of an inactive non-referenced element', function() { + fixtureSetup('
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an inactive non-referenced element with a duplicate', function() { + fixtureSetup('
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns true if the ID is of an active non-referenced element', function() { + fixtureSetup(''); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'button[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns true if the ID is a duplicate of an active non-referenced element', function() { + fixtureSetup('
' + ''); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an inactive ARIA referenced element', function() { + fixtureSetup('
' + '
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is a duplicate of an inactive ARIA referenced element', function() { + fixtureSetup( + '
' + + '
' + + '' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an active ARIA referenced element', function() { + fixtureSetup( + '' + '
' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'button[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is a duplicate of of an active ARIA referenced element', function() { + fixtureSetup( + '' + + '
' + + '' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); +}); diff --git a/test/rule-matches/duplicate-id-aria-matches.2.js b/test/rule-matches/duplicate-id-aria-matches.2.js new file mode 100644 index 0000000000..c6c49719de --- /dev/null +++ b/test/rule-matches/duplicate-id-aria-matches.2.js @@ -0,0 +1,79 @@ +describe('duplicate-id-mismatches', function() { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var fixtureSetup = axe.testUtils.fixtureSetup; + var rule; + + beforeEach(function() { + rule = axe._audit.rules.find(function(rule) { + return rule.id === 'duplicate-id-aria'; + }); + }); + + afterEach(function() { + fixture.innerHTML = ''; + }); + + it('is a function', function() { + assert.isFunction(rule.matches); + }); + + it('returns false if the ID is of an inactive non-referenced element', function() { + fixtureSetup('
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an inactive non-referenced element with a duplicate', function() { + fixtureSetup('
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an active non-referenced element', function() { + fixtureSetup(''); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'button[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is a duplicate of an active non-referenced element', function() { + fixtureSetup('
' + ''); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns true if the ID is of an inactive ARIA referenced element', function() { + fixtureSetup('
' + '
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns true if the ID is a duplicate of an inactive ARIA referenced element', function() { + fixtureSetup( + '
' + + '
' + + '' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns true if the ID is of an active ARIA referenced element', function() { + fixtureSetup( + '' + '
' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'button[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns true if the ID is a duplicate of of an active ARIA referenced element', function() { + fixtureSetup( + '' + + '
' + + '' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); +}); diff --git a/test/rule-matches/duplicate-id-misc-matches.js b/test/rule-matches/duplicate-id-misc-matches.js new file mode 100644 index 0000000000..c3b8ffffda --- /dev/null +++ b/test/rule-matches/duplicate-id-misc-matches.js @@ -0,0 +1,79 @@ +describe('duplicate-id-mismatches', function() { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var fixtureSetup = axe.testUtils.fixtureSetup; + var rule; + + beforeEach(function() { + rule = axe._audit.rules.find(function(rule) { + return rule.id === 'duplicate-id'; + }); + }); + + afterEach(function() { + fixture.innerHTML = ''; + }); + + it('is a function', function() { + assert.isFunction(rule.matches); + }); + + it('returns true if the ID is of an inactive non-referenced element', function() { + fixtureSetup('
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns true if the ID is of an inactive non-referenced element with a duplicate', function() { + fixtureSetup('
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isTrue(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an active non-referenced element', function() { + fixtureSetup(''); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'button[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is a duplicate of an active non-referenced element', function() { + fixtureSetup('
' + ''); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an inactive ARIA referenced element', function() { + fixtureSetup('
' + '
'); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'div[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is a duplicate of an inactive ARIA referenced element', function() { + fixtureSetup( + '
' + + '
' + + '' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is of an active ARIA referenced element', function() { + fixtureSetup( + '' + '
' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'button[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); + + it('returns false if the ID is a duplicate of of an active ARIA referenced element', function() { + fixtureSetup( + '' + + '
' + + '' + ); + var vNode = axe.utils.querySelectorAll(axe._tree[0], 'span[id=foo]')[0]; + assert.isFalse(rule.matches(vNode.actualNode, vNode)); + }); +});