-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React 16 fragments (rendering arrays and strings) unsupported #1213
Comments
Or more simply put: test('React 16 - fragments', () => {
const Fragments = () => [
<li key="1">First item</li>,
<li key="2">Second item</li>,
<li key="3">Third item</li>
];
const fragments = shallow(<Fragments />);
console.log('fragments:', fragments.debug());
});
test('React 16 - no fragments', () => {
const noFragments = shallow(
<div>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</div>
);
console.log('noFragments:', noFragments.debug());
}); Output:
|
Enzyme 3 fails with fragments, see enzymejs/enzyme#1213 (comment)
Enzyme 3 fails with fragments, see enzymejs/enzyme#1213 (comment)
Enzyme 3 fails with fragments, see enzymejs/enzyme#1213 (comment)
Enzyme 3 fails with fragments, see enzymejs/enzyme#1213 (comment)
Enzyme 3 fails with fragments, see enzymejs/enzyme#1213 (comment)
Enzyme 3 fails with fragments, see enzymejs/enzyme#1213 (comment)
I have very similar issue, also related to if component doesn't have a wrapping element. I'm using React 16. const StepsComponent = function StepsComponent({ steps }) {
return steps.map(function stepsMap(step) {
if (step.src) {
return <img src={step.src} alt={step.alt} />;
}
return step;
});
};
const steps = [
'123',
'another simple string',
{ src: './src/to/img', alt: 'img alt' },
];
const wrapper = mount(<StepsComponent steps={steps} />);
// The following only returns the img elements, it doesn't return simple nodes, such as strings.
const elements = wrapper.children().getElements();
// Now if I try this for shallow rendering, it won't even proceed
const wrapper2 = shallow(<StepsComponent steps={steps} />);
// This throws the following error:
// ShallowWrapper::getNode() can only be called when wrapping one node
const elements2 = wrapper.children().getElements(); If I wrap my component into a div, such as: const StepsComponent = function StepsComponent({ steps }) {
return (
<div>
{ steps.map(function stepsMap(step) {
if (step.src) {
return <img src={step.src} alt={step.alt} />;
}
return step;
}); }
</div>
);
}; Using shallow rendering on such component and then calling |
Temporary hack that seems to work well enough: const component = shallow(<FragmentComponent />)
const fragment = component.instance().render()
expect(shallow(<div>{fragment}</div>).getElement()).toMatchSnapshot() |
any update on this issue? or should I just wrap it in a div? |
This adds parent task bread crumbs to both the task show view and the focus view. **Notes** - Because `ParentTaskBreadCrumbs` is a recursive component (container -> component -> container -> component) they needed to be co-located in the same file. Having two files that each import the other was throwing an error for me. The component was coming in `undefined`. - Enzyme doesn't yet have support for "fragments", which were introduced in React 16, otherwise I'd have avoided the outer `span` in `ParentTaskBreadCrumbs` and just returned an array. I tried the hack mentioned in the linked thread, but it didn't work for me. enzymejs/enzyme#1213
React 16.2.0 arrived with new fragments syntax. Any progress here to support it by enzyme? |
TL;DR for now you can use it('always works', () => {
const subject = shallow(<Component />)
expect(subject.at(0).find('div').length).toBe(3)
}) class Frag extends React.Component {
render() {
return [
<div key='1' >1</div>,
<div key='2' >2</div>,
<div key='3' >3</div>,
]
}
}
class List extends React.Component {
render() {
const list = [1, 2, 3].map(n => <div key={n} >{n}</div>)
return list
}
}
class Norm extends React.Component {
render() {
return <div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
}
}
class NormList extends React.Component {
render() {
const list = [1, 2, 3].map(n => <div key={n}>{n}</div>)
return <div>
{list}
</div>
}
} describe('find()', () => {
it('Frag finds the divs', () => {
const subject = shallow(<Frag />)
expect(subject.at(0).find('div').length).toBe(3)
})
it('List finds the divs', () => {
const subject = shallow(<List />)
expect(subject.at(0).find('div').length).toBe(3)
})
it('Norm finds the divs', () => {
const subject = shallow(<Norm />)
expect(subject.at(0).find('div').length).toBe(4)
})
it('NormList finds the divs', () => {
const subject = shallow(<NormList />)
expect(subject.at(0).find('div').length).toBe(4)
})
})
describe('childen()', () => {
it('Frag finds the divs', () => {
const subject = shallow(<Frag />)
expect(subject.children().length).toBe(3)
})
it('List finds the divs', () => {
const subject = shallow(<List />)
expect(subject.children().length).toBe(3)
})
it('Norm finds the divs', () => {
const subject = shallow(<Norm />)
expect(subject.children().length).toBe(3)
})
it('NormList finds the divs', () => {
const subject = shallow(<NormList />)
expect(subject.children().find('div').length).toBe(3)
})
})
describe('at(0).children().find()', () => {
it('Frag finds the divs', () => {
const subject = shallow(<Frag />)
expect(subject.at(0).children().find('div').length).toBe(3)
})
it('List finds the divs', () => {
const subject = shallow(<List />)
expect(subject.at(0).children().find('div').length).toBe(3)
})
it('Norm finds the divs', () => {
const subject = shallow(<Norm />)
expect(subject.at(0).children().find('div').length).toBe(3)
})
it('NormList finds the divs', () => {
const subject = shallow(<NormList />)
expect(subject.at(0).children().find('div').length).toBe(3)
})
})
For what it's worth, I think the strangest thing here is that |
This morning, while reinstating fragments in my code base and testing them, I found yet another case that requires a bit of thinkering. class FragList extends React.Component {
render() {
const list = [1, 2, 3].map(n => <div key={n}>{n}</div>)
return [
<div role='List'>{list}</div>,
<div></div>
]
}
} it('FragList should have 2 outer divs and 3 inner', () => {
const subject = shallow(<FragList />)
expect(subject.at(0).find('div').length).toBe(2 + 3)
})
it('find the 2 outer divs using the technique above', () => {
const subject = shallow(<FragList />)
expect(subject.at(0).find('div').length).toBe(2)
})
it('find the divs inside the List', () => {
const subject = shallow(<FragList />)
const outer = subject.at(0).find('[role="List"]')
expect(outer.find('div').length).toBe(1 + 3)
})
it('find the divs inside the List!', () => {
const subject = shallow(<FragList />)
const outer = subject.at(0).find('[role="List"]')
expect(outer.at(0).find('div').length).toBe(1 + 3)
// ~~~~~~~~
})
it('find the divs inside the List!!!', () => {
const subject = shallow(<FragList />)
const outer = subject.at(0).find('[role="List"]')
expect(outer.shallow().at(0).find('div').length).toBe(1 + 3)
// ~~~~~~~~~~~~~~~
})
it('this also works...', () => {
const subject = shallow(<FragList />)
const outer = subject.at(0).find('[role="List"]')
expect(outer.shallow().find('div').length).toBe(1 + 3)
// ~~~~~~~~~~~
})
|
I have came across another issue when using Modal.component <Fragment>
<ModalBackdrop show={this.props.show} />
<ModalWrap show={this.props.show} onClick={this.outerClick}>
<ModalDialog show={this.props.show}>
<ModalContent>{this.props.children}</ModalContent>
</ModalDialog>
</ModalWrap>
</Fragment> Modal.test it('shoud do something', () => {
const component = mount(
<Modal show>
<div>YOLO</div>
</Modal>);
console.log(component.debug());
expect(component.find(ModalBackdrop).exists()).toBeTruthy(); ✓
expect(component.find(ModalWrap).exists()).toBeTruthy(); ✕
expect(component.find(ModalWrap).contains(ModalDialog)).toBeTruthy(); ✕
expect(component.find(ModalDialog).contains(ModalContent)).toBeTruthy(); ✕
});
<Modal show={true} serverRender={false} toggle={[Function]}>
<styled.div show={true}>
<div className="sc-bdVaJa ceVaxf" />
</styled.div>
</Modal> Running the same tests but changing the fragment to a div: Modal.component - <Fragment>
+ <div>
<ModalBackdrop show={this.props.show} />
<ModalWrap show={this.props.show} onClick={this.outerClick}>
<ModalDialog show={this.props.show}>
<ModalContent>{this.props.children}</ModalContent>
</ModalDialog>
</ModalWrap>
- </Fragment>
+ </div> Modal.test it('shoud do something', () => {
const component = mount(
<Modal show>
<div>YOLO</div>
</Modal>);
console.log(component.debug());
expect(component.find(ModalBackdrop).exists()).toBeTruthy(); ✓
expect(component.find(ModalWrap).exists()).toBeTruthy(); ✓
expect(component.find(ModalWrap).contains(ModalDialog)).toBeTruthy(); ✓
expect(component.find(ModalDialog).contains(ModalContent)).toBeTruthy(); ✓
}); And last but not least the output from debug
<Modal show={true} serverRender={false} toggle={[Function]}>
<div>
<styled.div show={true}>
<div className="sc-bdVaJa ceVaxf" />
</styled.div>
<styled.div show={true} onClick={[Function]}>
<div className="sc-iAyFgw cpEsdu" onClick={[Function]}>
<styled.div show={true}>
<div className="sc-kEYyzF gLtZUo">
<styled.div theme={{ ... }}>
<div className="sc-hMqMXs bjEXbE">
<div>
YOLO
</div>
</div>
</styled.div>
</div>
</styled.div>
</div>
</styled.div>
</div>
</Modal> |
Having the same issue as @FabioAntunes. I can see the correct output with .debug(), but my tests are failing because the second child of |
Much the same as others have mentioned, a rendering like: class Admin extends Component {
render () {
return (
<React.Fragment>
<h2>User Management</h2>
<p>You can search by name, email or ID.</p>
</React.Fragment>
)
}
} when const admin = mount(<Admin />)
console.log(admin.text()) # Logs --> User Management
console.log(admin.html()) # Logs --> <h2>User Management</h2> However, when |
Separate to my comment above, following the example used in Error Boundaries which renders like so: render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
} Rendering |
This issue is about fragment support; could you file a new issue for error boundary support? |
@ljharb Not the same thing? The ErrorBoundary is just an example -- the issue comes from the use of const Foo = function (props) {
return props.children
}
const eb = shallow(
<Foo>
<a className='dummy'>Link</a>
<a className='dummy'>Link 2</a>
</Foo>
)
# Calling .children() errors with ShallowWrapper::getNode() can only be called when wrapping one node
expect(eb.children()).toHaveLength(2) More than happy to create a separate issue but it seems to be the same as #1213 (comment). |
ah, fair point; returning children without passing it through |
Best solution I've found so far: wrap fragments with class MyComponent {
render() {
return (
<>
<div>Foo</div>
<div>Bar</div>
</>
);
}
}
class MyComponentEnzymeFix extends MyComponent {
render() {
return <div>{super.render()}</div>;
}
}
const wrapper = mount(<MyComponentEnzymeFix />); // Instead of mount(<MyComponent />)
// instead of .toEqual(' <div>Foo</div><div>Bar</div> ')
expect(wrapper.html()).toEqual('<div><div>Foo</div><div>Bar</div></div>'); |
An option for now is const TestWrapper = React.Fragment ? (
<React.Fragment>{this.props.children}</React.Fragment>
) : (
<div>{this.props.children}</div>
);
describe('if React.Fragment is available', () => {
before(() => {
Object.defineProperty(React, 'Fragment', {
configurable: true,
value: React.createClass({
render: function() {
return React.createElement(
'span',
{className: 'wrapper'},
Array.isArray(this.props.children) ?
this.props.children.map((el) => <span>{el}</span>) :
this.props.children
);
}
}),
});
});
after(() => {
Reflect.deleteProperty(React, 'Fragment');
});
it('should use React.Fragment component', () => {
const fragmentChildren = [
<p>Test123</p>,
<p>Test123</p>,
];
const component = mount(
<TestWrapper>
<fragmentChildren />
</TestWrapper>,
);
expect(component.find('span').is('span')).to.eql(true);
});
});
context('if React.Fragment is not available', () => {
it('should render a div', () => {
const component = mount(
<TestWrapper>
<p>Test123</p>
</TestWrapper>,
);
expect(component.find('div').is('div')).to.eql(true);
});
}); In that case, the |
For anyone who finds this issue and is using |
@danactive your original "failing test" will always fail - |
Looks like support for this just landed #1733 and now released :) |
If you do a
I would expect this to work properly... |
@heathzipongo what version of enzyme and which react adapter are you using? |
|
@heathzipongo Fragment support was added to enzyme in v3.4. Try upgrading. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I am getting error when testing function as a child component with has
Component with error
Error: Component without error:
|
You can shallow-render a component that renders a Fragment, but not a Fragment itself. Either way, that's unrelated to this issue, which is about rendering arrays and strings. |
React 16 added a new return type of an array with many fragments of JSX, see React 16 blog post
Enzyme v3.0.0 with React 16 adapter 1.0.0 throws an error with this sample code
Error
Failing test
Passing test
-=Dan=-
The text was updated successfully, but these errors were encountered: