Skip to content

Commit 9c562b1

Browse files
authored
Merge pull request #1781 from airbnb/fix_find_statefulness
- [Fix] `shallow`: `.parents`: ensure that one `.find` call does not affect another - [Fix] Freeze ROOT_NODES for child wrappers
2 parents f281fef + b2672d7 commit 9c562b1

5 files changed

Lines changed: 126 additions & 7 deletions

File tree

packages/enzyme-test-suite/test/ReactWrapper-spec.jsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3893,6 +3893,53 @@ describeWithDOM('mount', () => {
38933893
const formUp = input.parents('form');
38943894
expect(formUp).to.have.lengthOf(1);
38953895
});
3896+
3897+
it('works when called sequentially on two sibling nodes', () => {
3898+
class Test extends React.Component {
3899+
render() {
3900+
return (
3901+
<div>
3902+
<div className="a">
3903+
<div>A child</div>
3904+
</div>
3905+
<div className="b">
3906+
<div>B child</div>
3907+
</div>
3908+
</div>
3909+
);
3910+
}
3911+
}
3912+
3913+
const wrapper = mount(<Test />);
3914+
3915+
const aChild = wrapper.find({ children: 'A child' });
3916+
expect(aChild.debug()).to.equal(`<div>
3917+
A child
3918+
</div>`);
3919+
expect(aChild).to.have.lengthOf(1);
3920+
3921+
const bChild = wrapper.find({ children: 'B child' });
3922+
expect(bChild.debug()).to.equal(`<div>
3923+
B child
3924+
</div>`);
3925+
expect(bChild).to.have.lengthOf(1);
3926+
3927+
const bChildParents = bChild.parents('.b');
3928+
expect(bChildParents.debug()).to.equal(`<div className="b">
3929+
<div>
3930+
B child
3931+
</div>
3932+
</div>`);
3933+
expect(bChildParents).to.have.lengthOf(1);
3934+
3935+
const aChildParents = aChild.parents('.a');
3936+
expect(aChildParents.debug()).to.equal(`<div className="a">
3937+
<div>
3938+
A child
3939+
</div>
3940+
</div>`);
3941+
expect(aChildParents).to.have.lengthOf(1);
3942+
});
38963943
});
38973944

38983945
describe('.parent()', () => {

packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3556,6 +3556,53 @@ describe('shallow', () => {
35563556
expect(parents.at(0).hasClass('foo')).to.equal(true);
35573557
expect(parents.at(1).hasClass('bax')).to.equal(true);
35583558
});
3559+
3560+
it('works when called sequentially on two sibling nodes', () => {
3561+
class Test extends React.Component {
3562+
render() {
3563+
return (
3564+
<div>
3565+
<div className="a">
3566+
<div>A child</div>
3567+
</div>
3568+
<div className="b">
3569+
<div>B child</div>
3570+
</div>
3571+
</div>
3572+
);
3573+
}
3574+
}
3575+
3576+
const wrapper = shallow(<Test />);
3577+
3578+
const aChild = wrapper.find({ children: 'A child' });
3579+
expect(aChild.debug()).to.equal(`<div>
3580+
A child
3581+
</div>`);
3582+
expect(aChild).to.have.lengthOf(1);
3583+
3584+
const bChild = wrapper.find({ children: 'B child' });
3585+
expect(bChild.debug()).to.equal(`<div>
3586+
B child
3587+
</div>`);
3588+
expect(bChild).to.have.lengthOf(1);
3589+
3590+
const bChildParents = bChild.parents('.b');
3591+
expect(bChildParents.debug()).to.equal(`<div className="b">
3592+
<div>
3593+
B child
3594+
</div>
3595+
</div>`);
3596+
expect(bChildParents).to.have.lengthOf(1);
3597+
3598+
const aChildParents = aChild.parents('.a');
3599+
expect(aChildParents.debug()).to.equal(`<div className="a">
3600+
<div>
3601+
A child
3602+
</div>
3603+
</div>`);
3604+
expect(aChildParents).to.have.lengthOf(1);
3605+
});
35593606
});
35603607

35613608
describe('.parent()', () => {

packages/enzyme/src/RSTTraversal.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export function pathToNode(node, root) {
106106
}
107107

108108
export function parentsOfNode(node, root) {
109-
return pathToNode(node, root).reverse();
109+
return (pathToNode(node, root) || []).reverse();
110110
}
111111

112112
export function nodeHasId(node, id) {

packages/enzyme/src/ReactWrapper.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const RENDERER = sym('__renderer__');
3131
const UNRENDERED = sym('__unrendered__');
3232
const ROOT = sym('__root__');
3333
const OPTIONS = sym('__options__');
34+
const ROOT_NODES = sym('__rootNodes__');
3435

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

61+
function getRootNodeInternal(wrapper) {
62+
if (wrapper[ROOT].length !== 1) {
63+
throw new Error('getRootNodeInternal(wrapper) can only be called when wrapper wraps one node');
64+
}
65+
if (wrapper[ROOT] !== wrapper) {
66+
return wrapper[ROOT_NODES][0];
67+
}
68+
return wrapper[ROOT][NODE];
69+
}
70+
6071
function nodeParents(wrapper, node) {
61-
return parentsOfNode(node, wrapper[ROOT].getNodeInternal());
72+
return parentsOfNode(node, getRootNodeInternal(wrapper));
6273
}
6374

6475
function privateSetNodes(wrapper, nodes) {
@@ -102,6 +113,7 @@ class ReactWrapper {
102113
privateSet(this, RENDERER, root[RENDERER]);
103114
privateSet(this, ROOT, root);
104115
privateSetNodes(this, nodes);
116+
privateSet(this, ROOT_NODES, root[NODES]);
105117
}
106118
privateSet(this, OPTIONS, root ? root[OPTIONS] : options);
107119
}
@@ -639,7 +651,7 @@ class ReactWrapper {
639651
throw new TypeError('your adapter does not support `simulateError`. Try upgrading it!');
640652
}
641653

642-
const rootNode = this[ROOT].getNodeInternal();
654+
const rootNode = getRootNodeInternal(this);
643655
const nodeHierarchy = [thisNode].concat(nodeParents(this, thisNode));
644656
renderer.simulateError(nodeHierarchy, rootNode, error);
645657

@@ -737,8 +749,10 @@ class ReactWrapper {
737749
* @returns {ReactWrapper}
738750
*/
739751
parents(selector) {
740-
const allParents = this.wrap(this.single('parents', n => nodeParents(this, n)));
741-
return selector ? allParents.filter(selector) : allParents;
752+
return this.single('parents', (n) => {
753+
const allParents = this.wrap(nodeParents(this, n));
754+
return selector ? allParents.filter(selector) : allParents;
755+
});
742756
}
743757

744758
/**

packages/enzyme/src/ShallowWrapper.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const UNRENDERED = sym('__unrendered__');
3737
const ROOT = sym('__root__');
3838
const OPTIONS = sym('__options__');
3939
const SET_STATE = sym('__setState__');
40+
const ROOT_NODES = sym('__rootNodes__');
41+
4042
/**
4143
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
4244
* function.
@@ -144,6 +146,12 @@ function getRootNode(node) {
144146
}
145147

146148
function getRootNodeInternal(wrapper) {
149+
if (wrapper[ROOT].length !== 1) {
150+
throw new Error('getRootNodeInternal(wrapper) can only be called when wrapper wraps one node');
151+
}
152+
if (wrapper[ROOT] !== wrapper) {
153+
return wrapper[ROOT_NODES][0];
154+
}
147155
return wrapper[ROOT][NODE];
148156
}
149157

@@ -219,6 +227,7 @@ class ShallowWrapper {
219227
privateSet(this, RENDERER, root[RENDERER]);
220228
privateSetNodes(this, nodes);
221229
privateSet(this, OPTIONS, root[OPTIONS]);
230+
privateSet(this, ROOT_NODES, root[NODES]);
222231
}
223232
}
224233

@@ -972,8 +981,10 @@ class ShallowWrapper {
972981
* @returns {ShallowWrapper}
973982
*/
974983
parents(selector) {
975-
const allParents = this.wrap(this.single('parents', n => nodeParents(this, n)));
976-
return selector ? allParents.filter(selector) : allParents;
984+
return this.single('parents', (n) => {
985+
const allParents = this.wrap(nodeParents(this, n));
986+
return selector ? allParents.filter(selector) : allParents;
987+
});
977988
}
978989

979990
/**

0 commit comments

Comments
 (0)