diff --git a/src/Utils.js b/src/Utils.js index 8b31148d1..c011c9ad7 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -165,14 +165,31 @@ export function splitSelector(selector) { return selector.split(/(?=\.|\[.*\])|(?=#|\[#.*\])/); } -export function isSimpleSelector(selector) { - // any of these characters pretty much guarantee it's a complex selector - return !/[~\s:>]/.test(selector); + +const containsQuotes = /'|"/; +const containsColon = /:/; + + +export function isPsuedoClassSelector(selector) { + if (containsColon.test(selector)) { + if (!containsQuotes.test(selector)) { + return true; + } + const tokens = selector.split(containsQuotes); + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if (containsColon.test(token) && i % 2 === 0) { + return true; + } + } + return false; + } + return false; } -export function selectorError(selector) { +export function selectorError(selector, type = '') { return new TypeError( - `Enzyme received a complex CSS selector ('${selector}') that it does not currently support` + `Enzyme received a ${type} CSS selector ('${selector}') that it does not currently support` ); } @@ -187,6 +204,9 @@ export const SELECTOR = { }; export function selectorType(selector) { + if (isPsuedoClassSelector(selector)) { + throw selectorError(selector, 'psudo-class'); + } if (selector[0] === '.') { return SELECTOR.CLASS_TYPE; } else if (selector[0] === '#') { diff --git a/test/ReactWrapper-spec.jsx b/test/ReactWrapper-spec.jsx index 36d3911be..2adb8465e 100644 --- a/test/ReactWrapper-spec.jsx +++ b/test/ReactWrapper-spec.jsx @@ -390,12 +390,12 @@ describeWithDOM('mount', () => { React.createElement('div', null, React.createElement('span', { '123-foo': 'bar', '-foo': 'bar', - ':foo': 'bar', + '+foo': 'bar', })) ); expect(wrapper.find('[-foo]')).to.have.length(0, '-foo'); - expect(wrapper.find('[:foo]')).to.have.length(0, ':foo'); + expect(wrapper.find('[+foo]')).to.have.length(0, '+foo'); expect(wrapper.find('[123-foo]')).to.have.length(0, '123-foo'); }); diff --git a/test/Utils-spec.jsx b/test/Utils-spec.jsx index 358eed824..ca6215a05 100644 --- a/test/Utils-spec.jsx +++ b/test/Utils-spec.jsx @@ -9,7 +9,7 @@ import { coercePropValue, getNode, nodeEqual, - isSimpleSelector, + isPsuedoClassSelector, propFromEvent, SELECTOR, selectorType, @@ -246,39 +246,43 @@ describe('Utils', () => { }); - describe('isSimpleSelector', () => { + describe('isPsuedoClassSelector', () => { + describe('prohibited selectors', () => { - function isComplex(selector) { + function isNotPsuedo(selector) { it(selector, () => { - expect(isSimpleSelector(selector)).to.equal(false); + expect(isPsuedoClassSelector(selector)).to.equal(false); }); } - - isComplex('.foo .bar'); - isComplex(':visible'); - isComplex('.foo>.bar'); - isComplex('.foo > .bar'); - isComplex('.foo~.bar'); - + isNotPsuedo('.foo'); + isNotPsuedo('div'); + isNotPsuedo('.foo .bar'); + isNotPsuedo('[hover]'); + isNotPsuedo('[checked=""]'); + isNotPsuedo('[checked=":checked"]'); + isNotPsuedo('[checked=\':checked\']'); + isNotPsuedo('.foo>.bar'); + isNotPsuedo('.foo > .bar'); + isNotPsuedo('.foo~.bar'); + isNotPsuedo('#foo'); }); describe('allowed selectors', () => { - function isSimple(selector) { + function isPsuedo(selector) { it(selector, () => { - expect(isSimpleSelector(selector)).to.equal(true); + expect(isPsuedoClassSelector(selector)).to.equal(true); }); } - - isSimple('.foo'); - isSimple('.foo-and-foo'); - isSimple('input[foo="bar"]'); - isSimple('input[foo="bar"][bar="baz"][baz="foo"]'); - isSimple('.FoOaNdFoO'); - isSimple('tag'); - isSimple('.foo.bar'); - isSimple('input.foo'); - + isPsuedo(':checked'); + isPsuedo(':focus'); + isPsuedo(':hover'); + isPsuedo(':disabled'); + isPsuedo(':any'); + isPsuedo(':last-child'); + isPsuedo(':nth-child(1)'); + isPsuedo('div:checked'); + isPsuedo('[data-foo=":hover"]:hover'); }); });