Skip to content

Commit

Permalink
Remove old predicate/selector parsing utilities
Browse files Browse the repository at this point in the history
  • Loading branch information
Brandon Dail committed Sep 13, 2017
1 parent d7abfbe commit 8bffb8a
Show file tree
Hide file tree
Showing 4 changed files with 1 addition and 232 deletions.
37 changes: 0 additions & 37 deletions packages/enzyme-test-suite/test/RSTTraversal-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import './_helpers/setupAdapters';
import React from 'react';
import sinon from 'sinon';
import { expect } from 'chai';
import {
splitSelector,
} from 'enzyme/build/Utils';
import { elementToTree } from 'enzyme-adapter-utils';
import {
hasClassName,
Expand All @@ -13,39 +10,13 @@ import {
treeFilter,
pathToNode,
getTextFromNode,
buildPredicate,
} from 'enzyme/build/RSTTraversal';
import { describeIf } from './_helpers';
import { REACT013 } from './_helpers/version';

const $ = elementToTree;

describe('RSTTraversal', () => {
describe('splitSelector', () => {
const fn = splitSelector;
it('splits multiple class names', () => {
expect(fn('.foo.bar')).to.eql(['.foo', '.bar']);
expect(fn('.foo.bar.baz')).to.eql(['.foo', '.bar', '.baz']);
});

it('splits tag names and class names', () => {
expect(fn('input.bar')).to.eql(['input', '.bar']);
expect(fn('div.bar.baz')).to.eql(['div', '.bar', '.baz']);
expect(fn('Foo.bar')).to.eql(['Foo', '.bar']);
});

it('splits tag names and attributes', () => {
expect(fn('input[type="text"]')).to.eql(['input', '[type="text"]']);
expect(
fn('div[title="title"][data-value="foo"]'),
).to.eql(['div', '[title="title"]', '[data-value="foo"]']);
});

it('throws for malformed selectors', () => {
expect(() => fn('div[data-name="xyz"')).to.throw(/Enzyme::Selector received what appears to be a malformed string selector/);
});
});

describe('hasClassName', () => {

it('should work for standalone classNames', () => {
Expand Down Expand Up @@ -358,12 +329,4 @@ describe('RSTTraversal', () => {
});
});
});

describe('buildPredicate', () => {
it('should throw expected error', () => {
const intSelector = 10;
const func = buildPredicate.bind(this, intSelector);
expect(func).to.throw(TypeError, 'Enzyme::Selector expects a string, object, or Component Constructor');
});
});
});
65 changes: 0 additions & 65 deletions packages/enzyme-test-suite/test/Utils-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import {
childrenToSimplifiedArray,
nodeEqual,
nodeMatches,
isPseudoClassSelector,
SELECTOR,
selectorType,
displayNameOfNode,
} from 'enzyme/build/Utils';
import {
Expand Down Expand Up @@ -439,68 +436,6 @@ describe('Utils', () => {
});
});


describe('isPseudoClassSelector', () => {
describe('prohibited selectors', () => {
function isNotPseudo(selector) {
it(selector, () => {
expect(isPseudoClassSelector(selector)).to.equal(false);
});
}
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 isPseudo(selector) {
it(selector, () => {
expect(isPseudoClassSelector(selector)).to.equal(true);
});
}
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');
});
});

describe('selectorType', () => {
it('returns CLASS_TYPE for a prefixed .', () => {
const type = selectorType('.foo');

expect(type).to.be.equal(SELECTOR.CLASS_TYPE);
});

it('returns ID_TYPE for a prefixed #', () => {
const type = selectorType('#foo');

expect(type).to.be.equal(SELECTOR.ID_TYPE);
});

it('returns PROP_TYPE for []', () => {
function isProp(selector) {
expect(selectorType(selector)).to.be.equal(SELECTOR.PROP_TYPE);
}

isProp('[foo]');
isProp('[foo="bar"]');
});
});

describe('coercePropValue', () => {
const key = 'foo';
it('returns undefined if passed undefined', () => {
Expand Down
54 changes: 1 addition & 53 deletions packages/enzyme/src/RSTTraversal.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import isEmpty from 'lodash/isEmpty';
import flatten from 'lodash/flatten';
import isSubset from 'is-subset';
import functionName from 'function.prototype.name';
import {
splitSelector,
isCompoundSelector,
selectorType,
AND,
SELECTOR,
nodeHasType,
nodeHasProperty,
} from './Utils';
import { nodeHasProperty } from './Utils';

export function propsOfNode(node) {
return (node && node.props) || {};
Expand Down Expand Up @@ -111,49 +102,6 @@ export function nodeMatchesObjectProps(node, props) {
return isSubset(propsOfNode(node), props);
}

export function buildPredicate(selector) {
switch (typeof selector) {
case 'function':
// selector is a component constructor
return node => node && node.type === selector;

case 'string':
if (isCompoundSelector.test(selector)) {
return AND(splitSelector(selector).map(buildPredicate));
}

switch (selectorType(selector)) {
case SELECTOR.CLASS_TYPE:
return node => hasClassName(node, selector.slice(1));

case SELECTOR.ID_TYPE:
return node => nodeHasId(node, selector.slice(1));

case SELECTOR.PROP_TYPE: {
const propKey = selector.split(/\[([a-zA-Z][a-zA-Z_\d\-:]*?)(=|])/)[1];
const propValue = selector.split(/=(.*?)]/)[1];

return node => nodeHasProperty(node, propKey, propValue);
}
default:
// selector is a string. match to DOM tag or constructor displayName
return node => nodeHasType(node, selector);
}

case 'object':
if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) {
return node => nodeMatchesObjectProps(node, selector);
}
throw new TypeError(
'Enzyme::Selector does not support an array, null, or empty object as a selector',
);

default:
throw new TypeError('Enzyme::Selector expects a string, object, or Component Constructor');
}
}


export function getTextFromNode(node) {
if (node === null || node === undefined) {
return '';
Expand Down
77 changes: 0 additions & 77 deletions packages/enzyme/src/Utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint no-use-before-define:0 */
import isEqual from 'lodash/isEqual';
import is from 'object-is';
import uuidv4 from 'uuid/v4';
import entries from 'object.entries';
import functionName from 'function.prototype.name';
import configuration from './configuration';
Expand Down Expand Up @@ -206,82 +205,6 @@ export function withSetStateAllowed(fn) {
}
}

export function splitSelector(selector) {
// step 1: make a map of all quoted strings with a uuid
const quotedSegments = selector.split(/[^" ]+|("[^"]*")|.*/g)
.filter(Boolean)
.reduce((obj, match) => ({ ...obj, [match]: uuidv4() }), {});

const splits = selector
// step 2: replace all quoted strings with the uuid, so we don't have to properly parse them
.replace(/[^" ]+|("[^"]*")|.*/g, x => quotedSegments[x] || x)
// step 3: split as best we can without a proper parser
.split(/(?=\.|\[.*])|(?=#|\[#.*])/)
// step 4: restore the quoted strings by swapping back the uuid's for the original segments
.map((selectorSegment) => {
let restoredSegment = selectorSegment;
entries(quotedSegments).forEach(([k, v]) => {
restoredSegment = restoredSegment.replace(v, k);
});
return restoredSegment;
});

if (splits.length === 1 && splits[0] === selector) {
// splitSelector expects selector to be "splittable"
throw new TypeError('Enzyme::Selector received what appears to be a malformed string selector');
}

return splits;
}


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;
}

function selectorError(selector, type = '') {
return new TypeError(
`Enzyme received a ${type} CSS selector ('${selector}') that it does not currently support`,
);
}

export const isCompoundSelector = /^[.#]?-?[_a-z]+[_a-z0-9-]*[.[#]/i;

const isPropSelector = /^\[.*]$/;

export const SELECTOR = {
CLASS_TYPE: 0,
ID_TYPE: 1,
PROP_TYPE: 2,
};

export function selectorType(selector) {
if (isPseudoClassSelector(selector)) {
throw selectorError(selector, 'pseudo-class');
}
if (selector[0] === '.') {
return SELECTOR.CLASS_TYPE;
} else if (selector[0] === '#') {
return SELECTOR.ID_TYPE;
} else if (isPropSelector.test(selector)) {
return SELECTOR.PROP_TYPE;
}
return undefined;
}

export function AND(fns) {
const fnsReversed = fns.slice().reverse();
return x => fnsReversed.every(fn => fn(x));
Expand Down

0 comments on commit 8bffb8a

Please sign in to comment.