Skip to content

Commit

Permalink
Merge pull request #2001 from airbnb/mac--html-text-fragment-fixes
Browse files Browse the repository at this point in the history
[Fix] `mount`/`shallow`: `.text`/`.html`: handle an array of nodes properly
[enzyme-adapter-react-{16,16.1,16.2,16.3}] [New] Make nodeToHostNode handle arrays

Fixes #1799
  • Loading branch information
ljharb committed Feb 4, 2019
2 parents 58aefc8 + 1e24419 commit bcb6fa9
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 26 deletions.
17 changes: 12 additions & 5 deletions packages/enzyme-adapter-react-16.1/src/ReactSixteenOneAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,22 @@ function nodeToHostNode(_node) {
while (node && !Array.isArray(node) && node.instance === null) {
node = node.rendered;
}
if (Array.isArray(node)) {
// TODO(lmr): throw warning regarding not being able to get a host node here
throw new Error('Trying to get host node of an array');
}
// if the SFC returned null effectively, there is no host node.
if (!node) {
return null;
}
return ReactDOM.findDOMNode(node.instance);

const mapper = (item) => {
if (item && item.instance) return ReactDOM.findDOMNode(item.instance);
return null;
};
if (Array.isArray(node)) {
return node.map(mapper);
}
if (Array.isArray(node.rendered) && node.nodeType === 'class') {
return node.rendered.map(mapper);
}
return mapper(node);
}

const eventOptions = { animation: true };
Expand Down
17 changes: 12 additions & 5 deletions packages/enzyme-adapter-react-16.2/src/ReactSixteenTwoAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,22 @@ function nodeToHostNode(_node) {
while (node && !Array.isArray(node) && node.instance === null) {
node = node.rendered;
}
if (Array.isArray(node)) {
// TODO(lmr): throw warning regarding not being able to get a host node here
throw new Error('Trying to get host node of an array');
}
// if the SFC returned null effectively, there is no host node.
if (!node) {
return null;
}
return ReactDOM.findDOMNode(node.instance);

const mapper = (item) => {
if (item && item.instance) return ReactDOM.findDOMNode(item.instance);
return null;
};
if (Array.isArray(node)) {
return node.map(mapper);
}
if (Array.isArray(node.rendered) && node.nodeType === 'class') {
return node.rendered.map(mapper);
}
return mapper(node);
}

const eventOptions = { animation: true };
Expand Down
17 changes: 12 additions & 5 deletions packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,22 @@ function nodeToHostNode(_node) {
while (node && !Array.isArray(node) && node.instance === null) {
node = node.rendered;
}
if (Array.isArray(node)) {
// TODO(lmr): throw warning regarding not being able to get a host node here
throw new Error('Trying to get host node of an array');
}
// if the SFC returned null effectively, there is no host node.
if (!node) {
return null;
}
return ReactDOM.findDOMNode(node.instance);

const mapper = (item) => {
if (item && item.instance) return ReactDOM.findDOMNode(item.instance);
return null;
};
if (Array.isArray(node)) {
return node.map(mapper);
}
if (Array.isArray(node.rendered) && node.nodeType === 'class') {
return node.rendered.map(mapper);
}
return mapper(node);
}

const eventOptions = { animation: true };
Expand Down
17 changes: 12 additions & 5 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,22 @@ function nodeToHostNode(_node) {
while (node && !Array.isArray(node) && node.instance === null) {
node = node.rendered;
}
if (Array.isArray(node)) {
// TODO(lmr): throw warning regarding not being able to get a host node here
throw new Error('Trying to get host node of an array');
}
// if the SFC returned null effectively, there is no host node.
if (!node) {
return null;
}
return ReactDOM.findDOMNode(node.instance);

const mapper = (item) => {
if (item && item.instance) return ReactDOM.findDOMNode(item.instance);
return null;
};
if (Array.isArray(node)) {
return node.map(mapper);
}
if (Array.isArray(node.rendered) && node.nodeType === 'class') {
return node.rendered.map(mapper);
}
return mapper(node);
}

const eventOptions = {
Expand Down
62 changes: 62 additions & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3702,6 +3702,38 @@ describeWithDOM('mount', () => {
expect(wrapper.text()).to.equal('{ some text }');
});
});

describeIf(is('> 16.2'), 'fragments', () => {
class FragmentClassExample extends React.Component {
render() {
return (
<Fragment>
<div>Foo</div>
<div>Bar</div>
</Fragment>
);
}
}

const FragmentConstExample = () => (
<Fragment>
<div><span>Foo</span></div>
<div><span>Bar</span></div>
</Fragment>
);

it('correctly gets text for both children for class', () => {
const classWrapper = mount(<FragmentClassExample />);
expect(classWrapper.text()).to.include('Foo');
expect(classWrapper.text()).to.include('Bar');
});

it('correctly gets text for both children for const', () => {
const constWrapper = mount(<FragmentConstExample />);
expect(constWrapper.text()).to.include('Foo');
expect(constWrapper.text()).to.include('Bar');
});
});
});

describe('.props()', () => {
Expand Down Expand Up @@ -5135,6 +5167,36 @@ describeWithDOM('mount', () => {
expect(wrapper.find(Foo).html()).to.equal('<div class="in-foo"></div>');
});
});

describeIf(is('>16.2'), 'fragments', () => {
class FragmentClassExample extends React.Component {
render() {
return (
<Fragment>
<div><span>Foo</span></div>
<div><span>Bar</span></div>
</Fragment>
);
}
}

const FragmentConstExample = () => (
<Fragment>
<div><span>Foo</span></div>
<div><span>Bar</span></div>
</Fragment>
);

it('correctly renders html for both children for class', () => {
const classWrapper = mount(<FragmentClassExample />);
expect(classWrapper.html()).to.equal('<div><span>Foo</span></div><div><span>Bar</span></div>');
});

it('correctly renders html for both children for const', () => {
const constWrapper = mount(<FragmentConstExample />);
expect(constWrapper.html()).to.equal('<div><span>Foo</span></div><div><span>Bar</span></div>');
});
});
});

describe('.unmount()', () => {
Expand Down
62 changes: 62 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3653,6 +3653,38 @@ describe('shallow', () => {
expect(wrapper.text()).to.equal('{ some text }');
});
});

describeIf(is('> 16.2'), 'fragments', () => {
class FragmentClassExample extends React.Component {
render() {
return (
<Fragment>
<div>Foo</div>
<div>Bar</div>
</Fragment>
);
}
}

const FragmentConstExample = () => (
<Fragment>
<div><span>Foo</span></div>
<div><span>Bar</span></div>
</Fragment>
);

it('correctly gets text for both children for class', () => {
const classWrapper = shallow(<FragmentClassExample />);
expect(classWrapper.text()).to.include('Foo');
expect(classWrapper.text()).to.include('Bar');
});

it('correctly gets text for both children for const', () => {
const constWrapper = shallow(<FragmentConstExample />);
expect(constWrapper.text()).to.include('Foo');
expect(constWrapper.text()).to.include('Bar');
});
});
});

describe('.props()', () => {
Expand Down Expand Up @@ -5207,6 +5239,36 @@ describe('shallow', () => {
));
});
});

describeIf(is('>16.2'), 'fragments', () => {
class FragmentClassExample extends React.Component {
render() {
return (
<Fragment>
<div><span>Foo</span></div>
<div><span>Bar</span></div>
</Fragment>
);
}
}

const FragmentConstExample = () => (
<Fragment>
<div><span>Foo</span></div>
<div><span>Bar</span></div>
</Fragment>
);

it('correctly renders html for both children for class', () => {
const classWrapper = shallow(<FragmentClassExample />);
expect(classWrapper.html()).to.equal('<div><span>Foo</span></div><div><span>Bar</span></div>');
});

it('correctly renders html for both children for const', () => {
const constWrapper = shallow(<FragmentConstExample />);
expect(constWrapper.html()).to.equal('<div><span>Foo</span></div><div><span>Bar</span></div>');
});
});
});

describe('.unmount()', () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/enzyme/src/RSTTraversal.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,5 @@ export function getTextFromNode(node) {
return `<${node.type.displayName || functionName(node.type)} />`;
}

return childrenOfNode(node).map(getTextFromNode)
.join('');
return childrenOfNode(node).map(getTextFromNode).join('');
}
24 changes: 20 additions & 4 deletions packages/enzyme/src/ReactWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,16 @@ class ReactWrapper {
if (!node) {
return typeof n === 'string' ? n : node;
}
return node.textContent;

const nodeArray = Array.isArray(node) ? node : [node];
const textContent = nodeArray.map((item) => {
if (!item) {
return '';
}
return item.textContent || '';
});

return textContent.join('');
});
}

Expand All @@ -594,10 +603,17 @@ class ReactWrapper {
return this.single('html', (n) => {
if (n === null) return null;
const adapter = getAdapter(this[OPTIONS]);
const node = adapter.nodeToHostNode(n);
return node === null
const node = adapter.nodeToHostNode(n, true);

if (node === null) return null;

const nodeArray = Array.isArray(node) ? node : [node];
const nodesHTML = nodeArray.map(item => (item === null
? null
: node.outerHTML.replace(/\sdata-(reactid|reactroot)+="([^"]*)+"/g, '');
: item.outerHTML.replace(/\sdata-(reactid|reactroot)+="([^"]*)+"/g, '')
));

return nodesHTML.join('');
});
}

Expand Down

0 comments on commit bcb6fa9

Please sign in to comment.