From 6d521ba98c12739d6af9b6ffa1481cb283c25877 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Thu, 22 Sep 2022 16:01:09 -0600 Subject: [PATCH 1/4] fix(utils.matches): fix attribute exists selector to match empty attributes --- lib/core/utils/matches.js | 11 ++- test/core/utils/matches.js | 155 +++++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 69 deletions(-) diff --git a/lib/core/utils/matches.js b/lib/core/utils/matches.js index 412dfcf837..b8537cf199 100644 --- a/lib/core/utils/matches.js +++ b/lib/core/utils/matches.js @@ -16,7 +16,10 @@ function matchesAttributes(vNode, exp) { !exp.attributes || exp.attributes.every(att => { var nodeAtt = vNode.attr(att.key); - return nodeAtt !== null && (!att.value || att.test(nodeAtt)); + // console.log(JSON.stringify({att, nodeAtt},null,2)) + // console.log('nodeAtt !== null:', nodeAtt !== null) + // console.log('att.test(nodeAtt):', att.test(nodeAtt)) + return nodeAtt !== null && att.test(nodeAtt); }) ); } @@ -78,6 +81,8 @@ function convertAttributes(atts) { const attributeValue = (att.value || '').replace(reUnescape, ''); let test, regexp; + // console.log(JSON.stringify(att,null,2)); + switch (att.operator) { case '^=': regexp = new RegExp('^' + escapeRegExp(attributeValue)); @@ -108,9 +113,11 @@ function convertAttributes(atts) { return attributeValue !== value; }; break; + // attribute existence default: + // console.log('default case') test = value => { - return !!value; + return value !== null; }; } diff --git a/test/core/utils/matches.js b/test/core/utils/matches.js index ad5aac90dc..a82d780fdf 100644 --- a/test/core/utils/matches.js +++ b/test/core/utils/matches.js @@ -1,32 +1,32 @@ -describe('utils.matches', function() { +describe('utils.matches', function () { var matches = axe.utils.matches; var fixture = document.querySelector('#fixture'); var queryFixture = axe.testUtils.queryFixture; var convertSelector = axe._thisWillBeDeletedDoNotUse.utils.convertSelector; - afterEach(function() { + afterEach(function () { fixture.innerHTML = ''; }); - describe('tag', function() { - it('returns true if tag matches', function() { + describe('tag', function () { + it('returns true if tag matches', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1')); }); - it('returns false if the tag does not match', function() { + it('returns false if the tag does not match', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse(matches(virtualNode, 'div')); }); - it('is case sensitive for XHTML', function() { + it('is case sensitive for XHTML', function () { var virtualNode = queryFixture('

foo

'); delete virtualNode._cache.props; virtualNode._isXHTML = true; assert.isFalse(matches(virtualNode, 'h1')); }); - it('is case insensitive for HTML, but not for XHTML', function() { + it('is case insensitive for HTML, but not for XHTML', function () { var virtualNode = queryFixture('

foo

'); delete virtualNode._cache.props; virtualNode._isXHTML = true; @@ -34,22 +34,22 @@ describe('utils.matches', function() { }); }); - describe('classes', function() { - it('returns true if all classes match', function() { + describe('classes', function () { + it('returns true if all classes match', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '.foo.bar.baz')); }); - it('returns false if some classes do not match', function() { + it('returns false if some classes do not match', function () { var virtualNode = queryFixture( '' ); assert.isFalse(matches(virtualNode, '.foo.bar.bazz')); }); - it('returns false if any classes are missing', function() { + it('returns false if any classes are missing', function () { var virtualNode = queryFixture( '' ); @@ -57,168 +57,189 @@ describe('utils.matches', function() { }); }); - describe('attributes', function() { - it('returns true if attribute exists', function() { + describe('attributes', function () { + it('returns true if attribute exists', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '[foo]')); }); - it('returns true if attribute matches', function() { + it('returns true if attribute matches', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '[foo=baz]')); }); - it('returns true if all attributes match', function() { + it('returns true if all attributes match', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '[foo="baz"][bar="foo"][baz="bar"]')); }); - it('returns false if some attributes do not match', function() { + it('returns false if some attributes do not match', function () { var virtualNode = queryFixture( '' ); assert.isFalse(matches(virtualNode, '[foo="baz"][bar="foo"][baz="baz"]')); }); - it('returns false if any attributes are missing', function() { + it('returns false if any attributes are missing', function () { var virtualNode = queryFixture( '' ); assert.isFalse(matches(virtualNode, '[foo="baz"][bar="foo"][baz="bar"]')); }); - it('returns true if attribute starts with value', function() { + it('returns true if attribute starts with value', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '[foo^="baz"]')); }); - it('returns true if attribute ends with value', function() { + it('returns true if attribute ends with value', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '[foo$="hone"]')); }); - it('returns true if attribute contains value', function() { + it('returns true if attribute contains value', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '[foo*="baz"]')); }); - it('returns true if attribute has value', function() { + it('returns true if attribute has value', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, '[foo~="baz"]')); }); + + it('returns true if attribute matches not having value', function () { + var virtualNode = queryFixture( + '' + ); + assert.isTrue(matches(virtualNode, '[foo=""]')); + }); + + it('returns false if attribute should not have value', function () { + var virtualNode = queryFixture( + '' + ); + assert.isFalse(matches(virtualNode, '[foo=""]')); + }); + + it('should return true if attribute exists without value', function () { + var virtualNode = queryFixture( + '' + ); + assert.isTrue(matches(virtualNode, '[foo]')); + }); }); - describe('id', function() { - it('returns true if id matches', function() { + describe('id', function () { + it('returns true if id matches', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, '#target')); }); - it('returns false if the id does not match', function() { + it('returns false if the id does not match', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse(matches(virtualNode, '#notTarget')); }); }); - describe('pseudos', function() { - it('throws error if pseudo is not implemented', function() { + describe('pseudos', function () { + it('throws error if pseudo is not implemented', function () { var virtualNode = queryFixture('

foo

'); - assert.throws(function() { + assert.throws(function () { matches(virtualNode, 'h1:empty'); }); - assert.throws(function() { + assert.throws(function () { matches(virtualNode, 'h1::before'); }); }); - describe(':not', function() { - it('returns true if :not matches using tag', function() { + describe(':not', function () { + it('returns true if :not matches using tag', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:not(span)')); }); - it('returns true if :not matches using class', function() { + it('returns true if :not matches using class', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:not(.foo)')); }); - it('returns true if :not matches using attribute', function() { + it('returns true if :not matches using attribute', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:not([class])')); }); - it('returns true if :not matches using id', function() { + it('returns true if :not matches using id', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:not(#foo)')); }); - it('returns true if :not matches none of the selectors', function() { + it('returns true if :not matches none of the selectors', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:not([role=heading], span)')); }); - it('returns false if :not matches element', function() { + it('returns false if :not matches element', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse(matches(virtualNode, 'h1:not([id])')); }); - it('returns false if :not matches one of the selectors', function() { + it('returns false if :not matches one of the selectors', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse(matches(virtualNode, 'h1:not([role=heading], [id])')); }); }); - describe(':is', function() { - it('returns true if :is matches using tag', function() { + describe(':is', function () { + it('returns true if :is matches using tag', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, ':is(h1)')); }); - it('returns true if :is matches using class', function() { + it('returns true if :is matches using class', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:is(.foo)')); }); - it('returns true if :is matches using attribute', function() { + it('returns true if :is matches using attribute', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:is([class])')); }); - it('returns true if :is matches using id', function() { + it('returns true if :is matches using id', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:is(#target)')); }); - it('returns true if :is matches one of the selectors', function() { + it('returns true if :is matches one of the selectors', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, ':is([role=heading], h1)')); }); - it('returns true if :is matches complex selector', function() { + it('returns true if :is matches complex selector', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'h1:is(div > #target)')); }); - it('returns false if :is does not match element', function() { + it('returns false if :is does not match element', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse(matches(virtualNode, 'h1:is([class])')); }); - it('returns false if :is matches none of the selectors', function() { + it('returns false if :is matches none of the selectors', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse( matches(virtualNode, 'h1:is([class], span, #foo, .bar)') @@ -227,22 +248,22 @@ describe('utils.matches', function() { }); }); - describe('complex', function() { - it('returns true for complex selector', function() { + describe('complex', function () { + it('returns true for complex selector', function () { var virtualNode = queryFixture( '' ); assert.isTrue(matches(virtualNode, 'span.foo[id="target"]:not(div)')); }); - it('returns false if any part of the selector does not match', function() { + it('returns false if any part of the selector does not match', function () { var virtualNode = queryFixture( '' ); assert.isFalse(matches(virtualNode, 'span.foo[id="target"]:not(span)')); }); - it('returns true if a comma-separated list of selectors match', function() { + it('returns true if a comma-separated list of selectors match', function () { var virtualNode = queryFixture( '' ); @@ -250,92 +271,92 @@ describe('utils.matches', function() { }); }); - describe('combinator', function() { - it('returns true if parent selector matches', function() { + describe('combinator', function () { + it('returns true if parent selector matches', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'div > h1')); }); - it('returns true if nested parent selector matches', function() { + it('returns true if nested parent selector matches', function () { var virtualNode = queryFixture( '

foo

' ); assert.isTrue(matches(virtualNode, 'main > div > h1')); }); - it('returns true if hierarchical selector matches', function() { + it('returns true if hierarchical selector matches', function () { var virtualNode = queryFixture('

foo

'); assert.isTrue(matches(virtualNode, 'div h1')); }); - it('returns true if nested hierarchical selector matches', function() { + it('returns true if nested hierarchical selector matches', function () { var virtualNode = queryFixture( '

foo

' ); assert.isTrue(matches(virtualNode, 'div tr h1')); }); - it('returns true if mixed parent and hierarchical selector matches', function() { + it('returns true if mixed parent and hierarchical selector matches', function () { var virtualNode = queryFixture( '

foo

' ); assert.isTrue(matches(virtualNode, 'div tr > td h1')); }); - it('returns false if parent selector does not match', function() { + it('returns false if parent selector does not match', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse(matches(virtualNode, 'span > h1')); }); - it('returns false if nested parent selector does not match', function() { + it('returns false if nested parent selector does not match', function () { var virtualNode = queryFixture( '

foo

' ); assert.isFalse(matches(virtualNode, 'span > div > h1')); }); - it('returns false if hierarchical selector does not match', function() { + it('returns false if hierarchical selector does not match', function () { var virtualNode = queryFixture('

foo

'); assert.isFalse(matches(virtualNode, 'span h1')); }); - it('returns false if nested hierarchical selector does not match', function() { + it('returns false if nested hierarchical selector does not match', function () { var virtualNode = queryFixture( '

foo

' ); assert.isFalse(matches(virtualNode, 'div span h1')); }); - it('returns false if mixed parent and hierarchical selector does not match', function() { + it('returns false if mixed parent and hierarchical selector does not match', function () { var virtualNode = queryFixture( '

foo

' ); assert.isFalse(matches(virtualNode, 'div span > td h1')); }); - it('throws error if combinator is not implemented', function() { + it('throws error if combinator is not implemented', function () { var virtualNode = queryFixture('

foo

'); - assert.throws(function() { + assert.throws(function () { matches(virtualNode, 'div + h1'); }); - assert.throws(function() { + assert.throws(function () { matches(virtualNode, 'div ~ h1'); }); }); }); - describe('convertSelector', function() { - it('should set type to attrExist for attribute selector', function() { + describe('convertSelector', function () { + it('should set type to attrExist for attribute selector', function () { var expression = convertSelector('[disabled]'); assert.equal(expression[0][0].attributes[0].type, 'attrExist'); }); - it('should set type to attrValue for attribute value selector', function() { + it('should set type to attrValue for attribute value selector', function () { var expression = convertSelector('[aria-pressed="true"]'); assert.equal(expression[0][0].attributes[0].type, 'attrValue'); }); - it('should set type to attrValue for empty attribute value selector', function() { + it('should set type to attrValue for empty attribute value selector', function () { var expression = convertSelector('[aria-pressed=""]'); assert.equal(expression[0][0].attributes[0].type, 'attrValue'); }); From ac82a18467fa0fcce1b5e7f64cb474dd25de253a Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Thu, 22 Sep 2022 16:03:07 -0600 Subject: [PATCH 2/4] comments --- lib/core/utils/matches.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/core/utils/matches.js b/lib/core/utils/matches.js index b8537cf199..5a20573edf 100644 --- a/lib/core/utils/matches.js +++ b/lib/core/utils/matches.js @@ -16,9 +16,6 @@ function matchesAttributes(vNode, exp) { !exp.attributes || exp.attributes.every(att => { var nodeAtt = vNode.attr(att.key); - // console.log(JSON.stringify({att, nodeAtt},null,2)) - // console.log('nodeAtt !== null:', nodeAtt !== null) - // console.log('att.test(nodeAtt):', att.test(nodeAtt)) return nodeAtt !== null && att.test(nodeAtt); }) ); @@ -81,8 +78,6 @@ function convertAttributes(atts) { const attributeValue = (att.value || '').replace(reUnescape, ''); let test, regexp; - // console.log(JSON.stringify(att,null,2)); - switch (att.operator) { case '^=': regexp = new RegExp('^' + escapeRegExp(attributeValue)); @@ -115,7 +110,6 @@ function convertAttributes(atts) { break; // attribute existence default: - // console.log('default case') test = value => { return value !== null; }; From b5f6162a9aa70fa8e3cef2305331f022d79ea375 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Fri, 23 Sep 2022 08:17:55 -0600 Subject: [PATCH 3/4] Update test/core/utils/matches.js Co-authored-by: Wilco Fiers --- test/core/utils/matches.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/core/utils/matches.js b/test/core/utils/matches.js index a82d780fdf..9c98f226ec 100644 --- a/test/core/utils/matches.js +++ b/test/core/utils/matches.js @@ -128,6 +128,13 @@ describe('utils.matches', function () { assert.isTrue(matches(virtualNode, '[foo=""]')); }); + it('returns true if attribute matches not having value', function () { + var virtualNode = queryFixture( + '' + ); + assert.isTrue(matches(virtualNode, '[foo=""]')); + }); + it('returns false if attribute should not have value', function () { var virtualNode = queryFixture( '' From cb9df98392723d7701099e92517fa6bcf24c3269 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Fri, 23 Sep 2022 08:18:01 -0600 Subject: [PATCH 4/4] Update test/core/utils/matches.js Co-authored-by: Wilco Fiers --- test/core/utils/matches.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/utils/matches.js b/test/core/utils/matches.js index 9c98f226ec..6951243588 100644 --- a/test/core/utils/matches.js +++ b/test/core/utils/matches.js @@ -121,7 +121,7 @@ describe('utils.matches', function () { assert.isTrue(matches(virtualNode, '[foo~="baz"]')); }); - it('returns true if attribute matches not having value', function () { + it('returns true if attribute matches having an empty value', function () { var virtualNode = queryFixture( '' );