Skip to content

Commit

Permalink
Merge pull request #1781 from airbnb/fix_find_statefulness
Browse files Browse the repository at this point in the history
 - [Fix] `shallow`: `.parents`: ensure that one `.find` call does not affect another
 - [Fix] Freeze ROOT_NODES for child wrappers
  • Loading branch information
ljharb authored Sep 21, 2018
2 parents f281fef + b2672d7 commit 9c562b1
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 7 deletions.
47 changes: 47 additions & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3893,6 +3893,53 @@ describeWithDOM('mount', () => {
const formUp = input.parents('form');
expect(formUp).to.have.lengthOf(1);
});

it('works when called sequentially on two sibling nodes', () => {
class Test extends React.Component {
render() {
return (
<div>
<div className="a">
<div>A child</div>
</div>
<div className="b">
<div>B child</div>
</div>
</div>
);
}
}

const wrapper = mount(<Test />);

const aChild = wrapper.find({ children: 'A child' });
expect(aChild.debug()).to.equal(`<div>
A child
</div>`);
expect(aChild).to.have.lengthOf(1);

const bChild = wrapper.find({ children: 'B child' });
expect(bChild.debug()).to.equal(`<div>
B child
</div>`);
expect(bChild).to.have.lengthOf(1);

const bChildParents = bChild.parents('.b');
expect(bChildParents.debug()).to.equal(`<div className="b">
<div>
B child
</div>
</div>`);
expect(bChildParents).to.have.lengthOf(1);

const aChildParents = aChild.parents('.a');
expect(aChildParents.debug()).to.equal(`<div className="a">
<div>
A child
</div>
</div>`);
expect(aChildParents).to.have.lengthOf(1);
});
});

describe('.parent()', () => {
Expand Down
47 changes: 47 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3556,6 +3556,53 @@ describe('shallow', () => {
expect(parents.at(0).hasClass('foo')).to.equal(true);
expect(parents.at(1).hasClass('bax')).to.equal(true);
});

it('works when called sequentially on two sibling nodes', () => {
class Test extends React.Component {
render() {
return (
<div>
<div className="a">
<div>A child</div>
</div>
<div className="b">
<div>B child</div>
</div>
</div>
);
}
}

const wrapper = shallow(<Test />);

const aChild = wrapper.find({ children: 'A child' });
expect(aChild.debug()).to.equal(`<div>
A child
</div>`);
expect(aChild).to.have.lengthOf(1);

const bChild = wrapper.find({ children: 'B child' });
expect(bChild.debug()).to.equal(`<div>
B child
</div>`);
expect(bChild).to.have.lengthOf(1);

const bChildParents = bChild.parents('.b');
expect(bChildParents.debug()).to.equal(`<div className="b">
<div>
B child
</div>
</div>`);
expect(bChildParents).to.have.lengthOf(1);

const aChildParents = aChild.parents('.a');
expect(aChildParents.debug()).to.equal(`<div className="a">
<div>
A child
</div>
</div>`);
expect(aChildParents).to.have.lengthOf(1);
});
});

describe('.parent()', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/enzyme/src/RSTTraversal.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function pathToNode(node, root) {
}

export function parentsOfNode(node, root) {
return pathToNode(node, root).reverse();
return (pathToNode(node, root) || []).reverse();
}

export function nodeHasId(node, id) {
Expand Down
22 changes: 18 additions & 4 deletions packages/enzyme/src/ReactWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const RENDERER = sym('__renderer__');
const UNRENDERED = sym('__unrendered__');
const ROOT = sym('__root__');
const OPTIONS = sym('__options__');
const ROOT_NODES = sym('__rootNodes__');

/**
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
Expand All @@ -57,8 +58,18 @@ function filterWhereUnwrapped(wrapper, predicate) {
return wrapper.wrap(wrapper.getNodesInternal().filter(predicate).filter(Boolean));
}

function getRootNodeInternal(wrapper) {
if (wrapper[ROOT].length !== 1) {
throw new Error('getRootNodeInternal(wrapper) can only be called when wrapper wraps one node');
}
if (wrapper[ROOT] !== wrapper) {
return wrapper[ROOT_NODES][0];
}
return wrapper[ROOT][NODE];
}

function nodeParents(wrapper, node) {
return parentsOfNode(node, wrapper[ROOT].getNodeInternal());
return parentsOfNode(node, getRootNodeInternal(wrapper));
}

function privateSetNodes(wrapper, nodes) {
Expand Down Expand Up @@ -102,6 +113,7 @@ class ReactWrapper {
privateSet(this, RENDERER, root[RENDERER]);
privateSet(this, ROOT, root);
privateSetNodes(this, nodes);
privateSet(this, ROOT_NODES, root[NODES]);
}
privateSet(this, OPTIONS, root ? root[OPTIONS] : options);
}
Expand Down Expand Up @@ -639,7 +651,7 @@ class ReactWrapper {
throw new TypeError('your adapter does not support `simulateError`. Try upgrading it!');
}

const rootNode = this[ROOT].getNodeInternal();
const rootNode = getRootNodeInternal(this);
const nodeHierarchy = [thisNode].concat(nodeParents(this, thisNode));
renderer.simulateError(nodeHierarchy, rootNode, error);

Expand Down Expand Up @@ -737,8 +749,10 @@ class ReactWrapper {
* @returns {ReactWrapper}
*/
parents(selector) {
const allParents = this.wrap(this.single('parents', n => nodeParents(this, n)));
return selector ? allParents.filter(selector) : allParents;
return this.single('parents', (n) => {
const allParents = this.wrap(nodeParents(this, n));
return selector ? allParents.filter(selector) : allParents;
});
}

/**
Expand Down
15 changes: 13 additions & 2 deletions packages/enzyme/src/ShallowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const UNRENDERED = sym('__unrendered__');
const ROOT = sym('__root__');
const OPTIONS = sym('__options__');
const SET_STATE = sym('__setState__');
const ROOT_NODES = sym('__rootNodes__');

/**
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
* function.
Expand Down Expand Up @@ -144,6 +146,12 @@ function getRootNode(node) {
}

function getRootNodeInternal(wrapper) {
if (wrapper[ROOT].length !== 1) {
throw new Error('getRootNodeInternal(wrapper) can only be called when wrapper wraps one node');
}
if (wrapper[ROOT] !== wrapper) {
return wrapper[ROOT_NODES][0];
}
return wrapper[ROOT][NODE];
}

Expand Down Expand Up @@ -219,6 +227,7 @@ class ShallowWrapper {
privateSet(this, RENDERER, root[RENDERER]);
privateSetNodes(this, nodes);
privateSet(this, OPTIONS, root[OPTIONS]);
privateSet(this, ROOT_NODES, root[NODES]);
}
}

Expand Down Expand Up @@ -972,8 +981,10 @@ class ShallowWrapper {
* @returns {ShallowWrapper}
*/
parents(selector) {
const allParents = this.wrap(this.single('parents', n => nodeParents(this, n)));
return selector ? allParents.filter(selector) : allParents;
return this.single('parents', (n) => {
const allParents = this.wrap(nodeParents(this, n));
return selector ? allParents.filter(selector) : allParents;
});
}

/**
Expand Down

0 comments on commit 9c562b1

Please sign in to comment.