Skip to content

Commit

Permalink
Merge pull request #1157 from aweary/add-attribute-operators
Browse files Browse the repository at this point in the history
Support all attribute selector operators
  • Loading branch information
ljharb authored Nov 11, 2017
2 parents 2aaeecd + dec1505 commit f0fc012
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 114 deletions.
91 changes: 0 additions & 91 deletions packages/enzyme-test-suite/test/RSTTraversal-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { expect } from 'chai';
import { elementToTree } from 'enzyme-adapter-utils';
import {
hasClassName,
nodeHasProperty,
treeForEach,
treeFilter,
pathToNode,
Expand Down Expand Up @@ -46,96 +45,6 @@ describe('RSTTraversal', () => {
});
});

describe('nodeHasProperty', () => {
it('should find properties', () => {
function noop() {}
const node = $(<div onChange={noop} title="foo" />);

expect(nodeHasProperty(node, 'onChange')).to.equal(true);
expect(nodeHasProperty(node, 'title', 'foo')).to.equal(true);
});

it('should not match on html attributes', () => {
const node = $(<div htmlFor="foo" />);

expect(nodeHasProperty(node, 'for', 'foo')).to.equal(false);
});

it('should not find undefined properties', () => {
const node = $(<div title={undefined} />);

expect(nodeHasProperty(node, 'title')).to.equal(false);
});

it('should parse booleans', () => {
expect(nodeHasProperty(<div foo />, 'foo', true)).to.equal(true);
expect(nodeHasProperty(<div foo />, 'foo', false)).to.equal(false);
expect(nodeHasProperty(<div foo />, 'foo', 'true')).to.equal(false);
expect(nodeHasProperty(<div foo={false} />, 'foo', false)).to.equal(true);
expect(nodeHasProperty(<div foo={false} />, 'foo', true)).to.equal(false);
expect(nodeHasProperty(<div foo={false} />, 'foo', 'false')).to.equal(false);
});

it('should parse numeric literals', () => {
expect(nodeHasProperty(<div foo={2.3} />, 'foo', 2.3)).to.equal(true);
expect(nodeHasProperty(<div foo={2} />, 'foo', 2)).to.equal(true);
expect(nodeHasProperty(<div foo={2} />, 'foo', '2abc')).to.equal(false);
expect(nodeHasProperty(<div foo={2} />, 'foo', 'abc2')).to.equal(false);
expect(nodeHasProperty(<div foo={-2} />, 'foo', -2)).to.equal(true);
expect(nodeHasProperty(<div foo={2e8} />, 'foo', 2e8)).to.equal(true);
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', Infinity)).to.equal(true);
expect(nodeHasProperty(<div foo={-Infinity} />, 'foo', -Infinity)).to.equal(true);
});

it('should parse zeroes properly', () => {
expect(nodeHasProperty(<div foo={0} />, 'foo', 0)).to.equal(true);
expect(nodeHasProperty(<div foo={0} />, 'foo', +0)).to.equal(true);
expect(nodeHasProperty(<div foo={-0} />, 'foo', -0)).to.equal(true);
expect(nodeHasProperty(<div foo={-0} />, 'foo', 0)).to.equal(false);
expect(nodeHasProperty(<div foo={0} />, 'foo', -0)).to.equal(false);
expect(nodeHasProperty(<div foo={1} />, 'foo', 0)).to.equal(false);
expect(nodeHasProperty(<div foo={2} />, 'foo', -0)).to.equal(false);
});

it('should work with empty strings', () => {
expect(nodeHasProperty(<div foo="" />, 'foo', '')).to.equal(true);
expect(nodeHasProperty(<div foo={''} />, 'foo', '')).to.equal(true);
expect(nodeHasProperty(<div foo={'bar'} />, 'foo', '')).to.equal(false);
});

it('should work with NaN', () => {
expect(nodeHasProperty(<div foo={NaN} />, 'foo', NaN)).to.equal(true);
expect(nodeHasProperty(<div foo={0} />, 'foo', NaN)).to.equal(false);
});

it('should work with null', () => {
expect(nodeHasProperty(<div foo={null} />, 'foo', null)).to.equal(true);
expect(nodeHasProperty(<div foo={0} />, 'foo', null)).to.equal(false);
});

it('should work with false', () => {
expect(nodeHasProperty(<div foo={false} />, 'foo', false)).to.equal(true);
expect(nodeHasProperty(<div foo={0} />, 'foo', false)).to.equal(false);
});

it('should work with ±Infinity', () => {
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', Infinity)).to.equal(true);
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', +Infinity)).to.equal(true);
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', -Infinity)).to.equal(false);
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', 'Infinity')).to.equal(false);
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', NaN)).to.equal(false);
expect(nodeHasProperty(<div foo={0} />, 'foo', Infinity)).to.equal(false);
expect(nodeHasProperty(<div foo={-Infinity} />, 'foo', -Infinity)).to.equal(true);
expect(nodeHasProperty(<div foo={-Infinity} />, 'foo', Infinity)).to.equal(false);
expect(nodeHasProperty(<div foo={-Infinity} />, 'foo', Infinity)).to.equal(false);
expect(nodeHasProperty(<div foo={-Infinity} />, 'foo', '-Infinity')).to.equal(false);
expect(nodeHasProperty(<div foo={-Infinity} />, 'foo', NaN)).to.equal(false);
expect(nodeHasProperty(<div foo={NaN} />, 'foo', Infinity)).to.equal(false);
expect(nodeHasProperty(<div foo={NaN} />, 'foo', -Infinity)).to.equal(false);
expect(nodeHasProperty(<div foo={0} />, 'foo', -Infinity)).to.equal(false);
});
});

describe('treeForEach', () => {
it('should be called once for a leaf node', () => {
const spy = sinon.spy();
Expand Down
126 changes: 126 additions & 0 deletions packages/enzyme-test-suite/test/selector-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,16 @@ const tests = [
},
];

let expectAttributeMatch;

describe('selectors', () => {
tests.forEach(({ describeMethod, name, renderMethod }) => {
before(() => {
expectAttributeMatch = (element, selector, expected) => {
const wrapper = renderMethod(element);
expect(wrapper.is(selector)).to.equal(expected);
};
});
describeMethod(name, () => {
it('simple descendent', () => {
const wrapper = renderMethod((
Expand Down Expand Up @@ -350,6 +358,124 @@ describe('selectors', () => {
expect(wrapper.find('Wrapped(Foo)')).to.have.lengthOf(1);
expect(wrapper.find('Wrapped(Twice(Bar))')).to.have.lengthOf(1);
});

it('should parse booleans', () => {
expectAttributeMatch(<div hidden />, '[hidden=true]', true);
expectAttributeMatch(<div hidden />, '[hidden=false]', false);
expectAttributeMatch(<div hidden />, '[hidden="true"]', false);
expectAttributeMatch(<div hidden={false} />, '[hidden=false]', true);
expectAttributeMatch(<div hidden={false} />, '[hidden=true]', false);
expectAttributeMatch(<div hidden={false} />, '[hidden="false"]', false);
});

it('should parse numeric literals', () => {
expectAttributeMatch(<div data-foo={2.3} />, '[data-foo=2.3]', true);
expectAttributeMatch(<div data-foo={2} />, '[data-foo=2]', true);
expectAttributeMatch(<div data-foo={2} />, '[data-foo="2abc"]', false);
expectAttributeMatch(<div data-foo={2} />, '[data-foo="abc2"]', false);
expectAttributeMatch(<div data-foo={-2} />, '[data-foo=-2]', true);
// @TODO this is failing due to a parser issue
// expectAttributeMatch(<div data-foo={2e8} />, '[data-foo=2e8]', true);
expectAttributeMatch(<div data-foo={Infinity} />, '[data-foo=Infinity]', true);
expectAttributeMatch(<div data-foo={Infinity} />, '[data-foo=-Infinity]', false);
expectAttributeMatch(<div data-foo={-Infinity} />, '[data-foo=-Infinity]', true);
expectAttributeMatch(<div data-foo={-Infinity} />, '[data-foo=Infinity]', false);
});

it('should parse zeroes properly', () => {
expectAttributeMatch(<div data-foo={0} />, '[data-foo=0]', true);
expectAttributeMatch(<div data-foo={0} />, '[data-foo=+0]', true);
expectAttributeMatch(<div data-foo={-0} />, '[data-foo=-0]', true);
expectAttributeMatch(<div data-foo={-0} />, '[data-foo=0]', false);
expectAttributeMatch(<div data-foo={0} />, '[data-foo=-0]', false);
expectAttributeMatch(<div data-foo={1} />, '[data-foo=0]', false);
expectAttributeMatch(<div data-foo={2} />, '[data-foo=-0]', false);
});

it('should work with empty strings', () => {
expectAttributeMatch(<div className="" />, '[className=""]', true);
expectAttributeMatch(<div className={''} />, '[className=""]', true);
expectAttributeMatch(<div className={'bar'} />, '[className=""]', false);
});

it('should work with NaN', () => {
expectAttributeMatch(<div data-foo={NaN} />, '[data-foo=NaN]', true);
expectAttributeMatch(<div data-foo={0} />, '[data-foo=NaN]', false);
});

it('should work with null', () => {
expectAttributeMatch(<div data-foo={null} />, '[data-foo=null]', true);
expectAttributeMatch(<div data-foo={0} />, '[data-foo=null]', false);
});

it('should work with false', () => {
expectAttributeMatch(<div data-foo={false} />, '[data-foo=false]', true);
expectAttributeMatch(<div data-foo={0} />, '[data-foo=false]', false);
});
it('should work with ±Infinity', () => {
expectAttributeMatch(<div data-foo={Infinity} />, '[data-foo=Infinity]', true);
expectAttributeMatch(<div data-foo={Infinity} />, '[data-foo=+Infinity]', true);
expectAttributeMatch(<div data-foo={Infinity} />, '[data-foo=-Infinity]', false);
expectAttributeMatch(<div data-foo={Infinity} />, '[data-foo=NaN]', false);
expectAttributeMatch(<div data-foo={0} />, '[data-foo=Infinity]', false);
expectAttributeMatch(<div data-foo={0} />, '[data-foo=-Infinity]', false);
expectAttributeMatch(<div data-foo={-Infinity} />, '[data-foo=-Infinity]', true);
expectAttributeMatch(<div data-foo={-Infinity} />, '[data-foo=Infinity]', false);
expectAttributeMatch(<div data-foo={-Infinity} />, '[data-foo="-Infinity"]', false);
expectAttributeMatch(<div data-foo={-Infinity} />, '[data-foo=NaN]', false);
expectAttributeMatch(<div data-foo={NaN} />, '[data-foo=Infinity]', false);
expectAttributeMatch(<div data-foo={NaN} />, '[data-foo=-Infinity]', false);
});

it('whitespace list attribute selector', () => {
expectAttributeMatch(<div rel="foo bar baz" />, '[rel~="bar"]', true);
expectAttributeMatch(<div rel="foo bar baz" />, '[rel~="baz"]', true);
expectAttributeMatch(<div rel="foo bar baz" />, '[rel~="foo"]', true);
expectAttributeMatch(<div rel="foo bar baz" />, '[rel~="foo bar"]', false);
expectAttributeMatch(<div rel={1} />, '[rel~=1]', false);
expectAttributeMatch(<div rel="1" />, '[rel~=1]', false);
});

it('hypen attribute selector', () => {
expectAttributeMatch(<a hrefLang="en-US" />, '[hrefLang|="en"]', true);
expectAttributeMatch(<a hrefLang="en-US" />, '[hrefLang|="en-US"]', true);
expectAttributeMatch(<a hrefLang="en-US" />, '[hrefLang|="US"]', false);
expectAttributeMatch(<a hrefLang="en-US" />, '[hrefLang|="enUS"]', false);
expectAttributeMatch(<a hrefLang={1} />, '[hrefLang|=1]', false);
expectAttributeMatch(<a hrefLang="1" />, '[hrefLang|=1]', false);
});

it('prefix attribute operator', () => {
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src^="foo"]', true);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src^="foo-bar"]', true);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src^="foo-bar.jpg"]', true);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src^="bar"]', false);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src^=""]', false);
expectAttributeMatch(<img alt="" src={1} />, '[src^=1]', false);
expectAttributeMatch(<img alt="" src="1" />, '[src^=1]', false);
});

it('suffix attribute operator', () => {
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src$=".jpg"]', true);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src$="bar.jpg"]', true);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src$="foo-bar.jpg"]', true);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src$="foo"]', false);
expectAttributeMatch(<img alt="" src="foo-bar.jpg" />, '[src$=""]', false);
expectAttributeMatch(<img alt="" src={1} />, '[src$=1]', false);
expectAttributeMatch(<img alt="" src="1" />, '[src$=1]', false);
});

it('substring attribute operator', () => {
expectAttributeMatch(<div id="foo bar baz" />, '[id*="foo"]', true);
expectAttributeMatch(<div id="foo bar baz" />, '[id*="foo bar"]', true);
expectAttributeMatch(<div id="foo bar baz" />, '[id*="foo bar baz"]', true);
expectAttributeMatch(<div id="foo bar baz" />, '[id*="foo "]', true);
expectAttributeMatch(<div id="foo bar baz" />, '[id*="fo"]', true);
expectAttributeMatch(<div id="foo bar baz" />, '[id*="foz"]', false);
expectAttributeMatch(<div id="foo bar baz" />, '[id*=1]', false);
expectAttributeMatch(<div id={1} />, '[id*=1]', false);
expectAttributeMatch(<div id="1" />, '[id*=1]', false);
});
});
});
});
1 change: 1 addition & 0 deletions packages/enzyme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"dependencies": {
"cheerio": "^1.0.0-rc.2",
"function.prototype.name": "^1.0.3",
"has": "^1.0.1",
"is-subset": "^0.1.1",
"lodash": "^4.17.4",
"object-is": "^1.0.1",
Expand Down
21 changes: 1 addition & 20 deletions packages/enzyme/src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function isCustomComponentElement(inst, adapter) {
return !!inst && adapter.isValidElement(inst) && typeof inst.type === 'function';
}

function propsOfNode(node) {
export function propsOfNode(node) {
return entries((node && node.props) || {})
.filter(([, value]) => typeof value !== 'undefined')
.reduce((acc, [key, value]) => Object.assign(acc, { [key]: value }), {});
Expand Down Expand Up @@ -219,25 +219,6 @@ export function AND(fns) {
return x => fnsReversed.every(fn => fn(x));
}

export function nodeHasProperty(node, propKey, propValue) {
const nodeProps = propsOfNode(node);
const descriptor = Object.getOwnPropertyDescriptor(nodeProps, propKey);
if (descriptor && descriptor.get) {
return false;
}
const nodePropValue = nodeProps[propKey];

if (typeof nodePropValue === 'undefined') {
return false;
}

if (typeof propValue !== 'undefined') {
return is(nodePropValue, propValue);
}

return Object.prototype.hasOwnProperty.call(nodeProps, propKey);
}

export function displayNameOfNode(node) {
if (!node) return null;

Expand Down
Loading

0 comments on commit f0fc012

Please sign in to comment.