Skip to content

Commit 54498a0

Browse files
madicapljharb
authored andcommitted
accounts for Fragment type nodes
1 parent ff8fde2 commit 54498a0

File tree

4 files changed

+220
-9
lines changed

4 files changed

+220
-9
lines changed

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

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,63 @@ describeWithDOM('mount', () => {
11041104
expect(elements.filter('i')).to.have.lengthOf(2);
11051105
});
11061106
});
1107+
1108+
describeIf(is('>= 16.2'), 'with fragments', () => {
1109+
const NestedFragmentComponent = () => (
1110+
<div className="container">
1111+
<React.Fragment>
1112+
<span>A span</span>
1113+
<span>B span</span>
1114+
<div>A div</div>
1115+
<React.Fragment>
1116+
<span>C span</span>
1117+
</React.Fragment>
1118+
</React.Fragment>
1119+
<span>D span</span>
1120+
</div>
1121+
);
1122+
1123+
it('should find descendant span inside React.Fragment', () => {
1124+
const wrapper = mount(<NestedFragmentComponent />);
1125+
expect(wrapper.find('.container span')).to.have.lengthOf(4);
1126+
});
1127+
1128+
it('should not find nonexistent p inside React.Fragment', () => {
1129+
const wrapper = mount(<NestedFragmentComponent />);
1130+
expect(wrapper.find('.container p')).to.have.lengthOf(0);
1131+
});
1132+
1133+
it('should find direct child span inside React.Fragment', () => {
1134+
const wrapper = mount(<NestedFragmentComponent />);
1135+
expect(wrapper.find('.container > span')).to.have.lengthOf(4);
1136+
});
1137+
1138+
it('should handle adjacent sibling selector inside React.Fragment', () => {
1139+
const wrapper = mount(<NestedFragmentComponent />);
1140+
expect(wrapper.find('.container span + div')).to.have.lengthOf(1);
1141+
});
1142+
1143+
it('should handle general sibling selector inside React.Fragment', () => {
1144+
const wrapper = mount(<NestedFragmentComponent />);
1145+
expect(wrapper.find('.container div ~ span')).to.have.lengthOf(2);
1146+
});
1147+
1148+
itIf(is('>= 16.4.1'), 'should handle fragments with no content', () => {
1149+
const EmptyFragmentComponent = () => (
1150+
<div className="container">
1151+
<React.Fragment>
1152+
<React.Fragment />
1153+
</React.Fragment>
1154+
</div>
1155+
);
1156+
1157+
const wrapper = mount(<EmptyFragmentComponent />);
1158+
1159+
expect(wrapper.find('.container > span')).to.have.lengthOf(0);
1160+
expect(wrapper.find('.container span')).to.have.lengthOf(0);
1161+
expect(wrapper.children()).to.have.lengthOf(1);
1162+
});
1163+
});
11071164
});
11081165

11091166
describe('.findWhere(predicate)', () => {
@@ -1175,6 +1232,43 @@ describeWithDOM('mount', () => {
11751232
expect(foundNotSpan.type()).to.equal('i');
11761233
});
11771234

1235+
describeIf(is('>= 16.2'), 'with fragments', () => {
1236+
it('finds nodes', () => {
1237+
class FragmentFoo extends React.Component {
1238+
render() {
1239+
return (
1240+
<div>
1241+
<React.Fragment>
1242+
<span data-foo={this.props.selector} />
1243+
<i data-foo={this.props.selector} />
1244+
<React.Fragment>
1245+
<i data-foo={this.props.selector} />
1246+
</React.Fragment>
1247+
</React.Fragment>
1248+
<span data-foo={this.props.selector} />
1249+
</div>
1250+
);
1251+
}
1252+
}
1253+
1254+
const selector = 'blah';
1255+
const wrapper = mount(<FragmentFoo selector={selector} />);
1256+
const foundSpans = wrapper.findWhere(n => (
1257+
n.type() === 'span' && n.props()['data-foo'] === selector
1258+
));
1259+
expect(foundSpans).to.have.lengthOf(2);
1260+
expect(foundSpans.get(0).type).to.equal('span');
1261+
expect(foundSpans.get(1).type).to.equal('span');
1262+
1263+
const foundNotSpans = wrapper.findWhere(n => (
1264+
n.type() !== 'span' && n.props()['data-foo'] === selector
1265+
));
1266+
expect(foundNotSpans).to.have.lengthOf(2);
1267+
expect(foundNotSpans.get(0).type).to.equal('i');
1268+
expect(foundNotSpans.get(1).type).to.equal('i');
1269+
});
1270+
});
1271+
11781272
it('finds nodes when conditionally rendered', () => {
11791273
class Foo extends React.Component {
11801274
render() {

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

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import {
1717
import getAdapter from 'enzyme/build/getAdapter';
1818

1919
import './_helpers/setupAdapters';
20-
import { createClass, createContext } from './_helpers/react-compat';
20+
import {
21+
createClass,
22+
createContext,
23+
Fragment,
24+
} from './_helpers/react-compat';
2125
import {
2226
describeIf,
2327
itIf,
@@ -957,6 +961,62 @@ describe('shallow', () => {
957961
expect(elements.filter('i')).to.have.lengthOf(2);
958962
});
959963
});
964+
965+
describeIf(is('>= 16.2'), 'works with fragments', () => {
966+
const NestedFragmentComponent = () => (
967+
<div className="container">
968+
<Fragment>
969+
<span>A span</span>
970+
<span>B span</span>
971+
<div>A div</div>
972+
<Fragment>
973+
<span>C span</span>
974+
</Fragment>
975+
</Fragment>
976+
<span>D span</span>
977+
</div>
978+
);
979+
980+
it('should find descendant span inside React.Fragment', () => {
981+
const wrapper = shallow(<NestedFragmentComponent />);
982+
expect(wrapper.find('.container span')).to.have.lengthOf(4);
983+
});
984+
985+
it('should not find nonexistent p inside React.Fragment', () => {
986+
const wrapper = shallow(<NestedFragmentComponent />);
987+
expect(wrapper.find('.container p')).to.have.lengthOf(0);
988+
});
989+
990+
it('should find direct child span inside React.Fragment', () => {
991+
const wrapper = shallow(<NestedFragmentComponent />);
992+
expect(wrapper.find('.container > span')).to.have.lengthOf(4);
993+
});
994+
995+
it('should handle adjacent sibling selector inside React.Fragment', () => {
996+
const wrapper = shallow(<NestedFragmentComponent />);
997+
expect(wrapper.find('.container span + div')).to.have.lengthOf(1);
998+
});
999+
1000+
it('should handle general sibling selector inside React.Fragment', () => {
1001+
const wrapper = shallow(<NestedFragmentComponent />);
1002+
expect(wrapper.find('.container div ~ span')).to.have.lengthOf(2);
1003+
});
1004+
1005+
it('should handle fragments with no content', () => {
1006+
const EmptyFragmentComponent = () => (
1007+
<div className="container">
1008+
<Fragment>
1009+
<Fragment />
1010+
</Fragment>
1011+
</div>
1012+
);
1013+
const wrapper = shallow(<EmptyFragmentComponent />);
1014+
1015+
expect(wrapper.find('.container > span')).to.have.lengthOf(0);
1016+
expect(wrapper.find('.container span')).to.have.lengthOf(0);
1017+
expect(wrapper.children()).to.have.lengthOf(0);
1018+
});
1019+
});
9601020
});
9611021

9621022
describe('.findWhere(predicate)', () => {
@@ -1028,6 +1088,43 @@ describe('shallow', () => {
10281088
expect(foundNotSpan.type()).to.equal('i');
10291089
});
10301090

1091+
describeIf(is('>= 16.2'), 'with fragments', () => {
1092+
it('finds nodes', () => {
1093+
class FragmentFoo extends React.Component {
1094+
render() {
1095+
return (
1096+
<div>
1097+
<Fragment>
1098+
<span data-foo={this.props.selector} />
1099+
<i data-foo={this.props.selector} />
1100+
<Fragment>
1101+
<i data-foo={this.props.selector} />
1102+
</Fragment>
1103+
</Fragment>
1104+
<span data-foo={this.props.selector} />
1105+
</div>
1106+
);
1107+
}
1108+
}
1109+
1110+
const selector = 'blah';
1111+
const wrapper = shallow(<FragmentFoo selector={selector} />);
1112+
const foundSpans = wrapper.findWhere(n => (
1113+
n.type() === 'span' && n.props()['data-foo'] === selector
1114+
));
1115+
expect(foundSpans).to.have.lengthOf(2);
1116+
expect(foundSpans.get(0).type).to.equal('span');
1117+
expect(foundSpans.get(1).type).to.equal('span');
1118+
1119+
const foundNotSpans = wrapper.findWhere(n => (
1120+
n.type() !== 'span' && n.props()['data-foo'] === selector
1121+
));
1122+
expect(foundNotSpans).to.have.lengthOf(2);
1123+
expect(foundNotSpans.get(0).type).to.equal('i');
1124+
expect(foundNotSpans.get(1).type).to.equal('i');
1125+
});
1126+
});
1127+
10311128
it('finds nodes when conditionally rendered', () => {
10321129
class Foo extends React.Component {
10331130
render() {

packages/enzyme/src/RSTTraversal.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,33 @@ import flat from 'array.prototype.flat';
22
import entries from 'object.entries';
33
import isSubset from 'is-subset';
44
import functionName from 'function.prototype.name';
5+
import getAdapter from './getAdapter';
56

67
export function propsOfNode(node) {
78
return (node && node.props) || {};
89
}
910

1011
export function childrenOfNode(node) {
1112
if (!node) return [];
12-
return Array.isArray(node.rendered) ? flat(node.rendered, 1) : [node.rendered];
13+
14+
const adapter = getAdapter();
15+
const adapterHasIsFragment = adapter && adapter.isFragment && (typeof adapter.isFragment === 'function');
16+
17+
const renderedArray = Array.isArray(node.rendered) ? flat(node.rendered, 1) : [node.rendered];
18+
19+
// React adapters before 16 will not have isFragment
20+
if (!adapterHasIsFragment) {
21+
return renderedArray;
22+
}
23+
24+
return flat(renderedArray.map((currentChild) => {
25+
// If the node is a Fragment, we want to return its children, not the fragment itself
26+
if (adapter.isFragment(currentChild)) {
27+
return childrenOfNode(currentChild);
28+
}
29+
30+
return currentChild;
31+
}), 1);
1332
}
1433

1534
export function hasClassName(node, className) {
@@ -52,9 +71,8 @@ export function findParentNode(root, targetNode) {
5271
if (!node.rendered) {
5372
return false;
5473
}
55-
return Array.isArray(node.rendered)
56-
? node.rendered.indexOf(targetNode) !== -1
57-
: node.rendered === targetNode;
74+
75+
return childrenOfNode(node).indexOf(targetNode) !== -1;
5876
},
5977
);
6078
return results[0] || null;

packages/enzyme/src/selectors.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,9 @@ function matchAdjacentSiblings(nodes, predicate, root) {
287287
if (!parent) {
288288
return matches;
289289
}
290-
const nodeIndex = parent.rendered.indexOf(node);
291-
const adjacentSibling = parent.rendered[nodeIndex + 1];
290+
const parentChildren = childrenOfNode(parent);
291+
const nodeIndex = parentChildren.indexOf(node);
292+
const adjacentSibling = parentChildren[nodeIndex + 1];
292293
// No sibling
293294
if (!adjacentSibling) {
294295
return matches;
@@ -313,8 +314,9 @@ function matchGeneralSibling(nodes, predicate, root) {
313314
if (!parent) {
314315
return matches;
315316
}
316-
const nodeIndex = parent.rendered.indexOf(node);
317-
const youngerSiblings = parent.rendered.slice(nodeIndex + 1);
317+
const parentChildren = childrenOfNode(parent);
318+
const nodeIndex = parentChildren.indexOf(node);
319+
const youngerSiblings = parentChildren.slice(nodeIndex + 1);
318320
return matches.concat(youngerSiblings.filter(predicate));
319321
}, nodes);
320322
}

0 commit comments

Comments
 (0)