diff --git a/src/Utils.js b/src/Utils.js index 8b31148d1..15bc375bb 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -165,14 +165,27 @@ 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 isPseudoClassSelector(selector) { + if (containsColon.test(selector)) { + if (!containsQuotes.test(selector)) { + return true; + } + const tokens = selector.split(containsQuotes); + return tokens.some((token, i) => + containsColon.test(token) && i % 2 === 0 + ); + } + 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 +200,9 @@ export const SELECTOR = { }; export function selectorType(selector) { + if (isPseudoClassSelector(selector)) { + throw selectorError(selector, 'pseudo-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..808ad355b 100644 --- a/test/Utils-spec.jsx +++ b/test/Utils-spec.jsx @@ -9,7 +9,7 @@ import { coercePropValue, getNode, nodeEqual, - isSimpleSelector, + isPseudoClassSelector, propFromEvent, SELECTOR, selectorType, @@ -246,39 +246,43 @@ describe('Utils', () => { }); - describe('isSimpleSelector', () => { + describe('isPseudoClassSelector', () => { + describe('prohibited selectors', () => { - function isComplex(selector) { + function isNotPseudo(selector) { it(selector, () => { - expect(isSimpleSelector(selector)).to.equal(false); + expect(isPseudoClassSelector(selector)).to.equal(false); }); } - - isComplex('.foo .bar'); - isComplex(':visible'); - isComplex('.foo>.bar'); - isComplex('.foo > .bar'); - isComplex('.foo~.bar'); - + isNotPseudo('.foo'); + isNotPseudo('div'); + isNotPseudo('.foo .bar'); + isNotPseudo('[hover]'); + isNotPseudo('[checked=""]'); + isNotPseudo('[checked=":checked"]'); + isNotPseudo('[checked=\':checked\']'); + isNotPseudo('.foo>.bar'); + isNotPseudo('.foo > .bar'); + isNotPseudo('.foo~.bar'); + isNotPseudo('#foo'); }); describe('allowed selectors', () => { - function isSimple(selector) { + function isPseudo(selector) { it(selector, () => { - expect(isSimpleSelector(selector)).to.equal(true); + expect(isPseudoClassSelector(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'); - + isPseudo(':checked'); + isPseudo(':focus'); + isPseudo(':hover'); + isPseudo(':disabled'); + isPseudo(':any'); + isPseudo(':last-child'); + isPseudo(':nth-child(1)'); + isPseudo('div:checked'); + isPseudo('[data-foo=":hover"]:hover'); }); });