diff --git a/lib/core/utils/escape-selector.js b/lib/core/utils/escape-selector.js index 8d6b53ac72..4c7d945884 100644 --- a/lib/core/utils/escape-selector.js +++ b/lib/core/utils/escape-selector.js @@ -8,7 +8,7 @@ axe.utils.escapeSelector = function (value) { 'use strict'; /*eslint no-bitwise: 0, eqeqeq: 0, complexity: ["error",27], - max-statements:["error", 23], one-var: 0, -W041: 0 */ + max-statements:["error", 25], one-var: 0, -W041: 0 */ var string = String(value); var length = string.length; var index = -1; @@ -20,17 +20,18 @@ axe.utils.escapeSelector = function (value) { // Note: there’s no need to special-case astral symbols, surrogate // pairs, or lone surrogates. - // If the character is NULL (U+0000), then throw an - // `InvalidCharacterError` exception and terminate these steps. + // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER + // (U+FFFD). if (codeUnit == 0x0000) { - throw new Error('INVALID_CHARACTER_ERR'); + result += '\uFFFD'; + continue; } if ( - // If the character is in the range [\1-\1F] (U+0001 to U+001F) or - // [\7F-\9F] (U+007F to U+009F), […] - (codeUnit >= 0x0001 && codeUnit <= 0x001F) || - (codeUnit >= 0x007F && codeUnit <= 0x009F) || + // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is + // U+007F, […] + (codeUnit >= 0x0001 && codeUnit <= 0x001F) || + codeUnit == 0x007F || // If the character is the first character and is in the range [0-9] // (U+0030 to U+0039), […] (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || @@ -38,15 +39,14 @@ axe.utils.escapeSelector = function (value) { // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] (index == 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit == 0x002D) ) { - // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point + // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point result += '\\' + codeUnit.toString(16) + ' '; continue; } - // If the character is the second character and is `-` (U+002D) and the - // first character is `-` as well, […] - if (index == 1 && codeUnit == 0x002D && firstCodeUnit == 0x002D) { - // http://dev.w3.org/csswg/cssom/#escape-a-character + // If the character is the first character and is a `-` (U+002D), and + // there is no second character, […] + if (index == 0 && length == 1 && codeUnit == 0x002D) { result += '\\' + string.charAt(index); continue; } @@ -69,7 +69,7 @@ axe.utils.escapeSelector = function (value) { } // Otherwise, the escaped character. - // http://dev.w3.org/csswg/cssom/#escape-a-character + // https://drafts.csswg.org/cssom/#escape-a-character result += '\\' + string.charAt(index); } diff --git a/test/core/utils/escape-selector.js b/test/core/utils/escape-selector.js index f2c25fc127..0b785b1b39 100644 --- a/test/core/utils/escape-selector.js +++ b/test/core/utils/escape-selector.js @@ -3,20 +3,37 @@ describe('utils.escapeSelector', function () { 'use strict'; var escapeSelector = axe.utils.escapeSelector; - it('should serialize strings based on CSSOM spec', function () { + it('leaves characters that do not need to escape alone', function () { + assert.equal(escapeSelector('a0b'), 'a0b'); + assert.equal(escapeSelector('a1b'), 'a1b'); + assert.equal(escapeSelector('a2b'), 'a2b'); + assert.equal(escapeSelector('a3b'), 'a3b'); + assert.equal(escapeSelector('a4b'), 'a4b'); + assert.equal(escapeSelector('a5b'), 'a5b'); + assert.equal(escapeSelector('a6b'), 'a6b'); + assert.equal(escapeSelector('a7b'), 'a7b'); + assert.equal(escapeSelector('a8b'), 'a8b'); + assert.equal(escapeSelector('a9b'), 'a9b'); + assert.equal(escapeSelector('a0123456789b'), 'a0123456789b'); + assert.equal(escapeSelector('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz'); + assert.equal(escapeSelector('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); + }); - assert.throws(function () { escapeSelector('\0'); }, Error); - assert.throws(function () { escapeSelector('a\0'); }, Error); - assert.throws(function () { escapeSelector('a\0b'); }, Error); + it('escapes null characters', function () { + assert.equal(escapeSelector('\0'), '\uFFFD'); + assert.equal(escapeSelector('a\0'), 'a\uFFFD'); + assert.equal(escapeSelector('a\0b'), 'a\uFFFDb'); + }); + it('stringifies non-string characters', function () { assert.equal(escapeSelector(), 'undefined'); assert.equal(escapeSelector(true), 'true'); assert.equal(escapeSelector(false), 'false'); assert.equal(escapeSelector(null), 'null'); assert.equal(escapeSelector(''), ''); + }); - assert.equal(escapeSelector('\x01\x02\x1E\x1F'), '\\1 \\2 \\1e \\1f '); - + it('escapes strings starting with a number', function () { assert.equal(escapeSelector('0a'), '\\30 a'); assert.equal(escapeSelector('1a'), '\\31 a'); assert.equal(escapeSelector('2a'), '\\32 a'); @@ -27,18 +44,15 @@ describe('utils.escapeSelector', function () { assert.equal(escapeSelector('7a'), '\\37 a'); assert.equal(escapeSelector('8a'), '\\38 a'); assert.equal(escapeSelector('9a'), '\\39 a'); + }); - assert.equal(escapeSelector('a0b'), 'a0b'); - assert.equal(escapeSelector('a1b'), 'a1b'); - assert.equal(escapeSelector('a2b'), 'a2b'); - assert.equal(escapeSelector('a3b'), 'a3b'); - assert.equal(escapeSelector('a4b'), 'a4b'); - assert.equal(escapeSelector('a5b'), 'a5b'); - assert.equal(escapeSelector('a6b'), 'a6b'); - assert.equal(escapeSelector('a7b'), 'a7b'); - assert.equal(escapeSelector('a8b'), 'a8b'); - assert.equal(escapeSelector('a9b'), 'a9b'); + it('only escapes "-" when before a number, or on its own', function () { + assert.equal(escapeSelector('-123'), '-\\31 23'); + assert.equal(escapeSelector('-'), '\\-'); + assert.equal(escapeSelector('--a'), '--a'); + }); + it('escapes characters staring with a negative number', function () { assert.equal(escapeSelector('-0a'), '-\\30 a'); assert.equal(escapeSelector('-1a'), '-\\31 a'); assert.equal(escapeSelector('-2a'), '-\\32 a'); @@ -49,15 +63,13 @@ describe('utils.escapeSelector', function () { assert.equal(escapeSelector('-7a'), '-\\37 a'); assert.equal(escapeSelector('-8a'), '-\\38 a'); assert.equal(escapeSelector('-9a'), '-\\39 a'); + }); - assert.equal(escapeSelector('--a'), '-\\-a'); - - assert.equal(escapeSelector('\x80\x2D\x5F\xA9'), '\\80 \x2D\x5F\xA9'); + it('escapes hex character codes', function () { + assert.equal(escapeSelector('\x80\x2D\x5F\xA9'), '\x80\x2D\x5F\xA9'); assert.equal(escapeSelector('\xA0\xA1\xA2'), '\xA0\xA1\xA2'); - assert.equal(escapeSelector('a0123456789b'), 'a0123456789b'); - assert.equal(escapeSelector('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz'); - assert.equal(escapeSelector('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); + assert.equal(escapeSelector('\x01\x02\x1E\x1F'), '\\1 \\2 \\1e \\1f '); assert.equal(escapeSelector('\x20\x21\x78\x79'), '\\ \\!xy'); // astral symbol (U+1D306 TETRAGRAM FOR CENTRE) @@ -65,7 +77,6 @@ describe('utils.escapeSelector', function () { // lone surrogates assert.equal(escapeSelector('\uDF06'), '\uDF06'); assert.equal(escapeSelector('\uD834'), '\uD834'); - }); });