Skip to content

Commit ee180af

Browse files
committed
[enzyme-adapter-react-16] [New] mount: add hydrateIn option
Fixes #1316
1 parent 0b552b5 commit ee180af

File tree

4 files changed

+57
-25
lines changed

4 files changed

+57
-25
lines changed

docs/api/ReactWrapper/detach.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
Detaches the react tree from the DOM. Runs `ReactDOM.unmountComponentAtNode()` under the hood.
44

55
This method will most commonly be used as a "cleanup" method if you decide to use the
6-
`attachTo` option in `mount(node, options)`.
6+
`attachTo` or `hydrateIn` option in `mount(node, options)`.
77

88
The method is intentionally not "fluent" (in that it doesn't return `this`) because you should
99
not be doing anything with this wrapper after this method is called.
1010

11-
Using the `attachTo` is not generally recommended unless it is absolutely necessary to test
11+
Using `attachTo`/`hydrateIn` is not generally recommended unless it is absolutely necessary to test
1212
something. It is your responsibility to clean up after yourself at the end of the test if you do
1313
decide to use it, though.
1414

@@ -21,6 +21,10 @@ With the `attachTo` option, you can mount components to attached DOM elements:
2121
// render a component directly into document.body
2222
const wrapper = mount(<Bar />, { attachTo: document.body });
2323

24+
// Or, with the `hydrateIn` option, you can mount components on top of existing DOM elements:
25+
// hydrate a component directly onto document.body
26+
const hydratedWrapper = mount(<Bar />, { hydrateIn: document.body });
27+
2428
// we can see that the component is rendered into the document
2529
expect(wrapper.find('.in-bar')).to.have.length(1);
2630
expect(document.body.childNodes).to.have.length(1);
@@ -44,6 +48,8 @@ expect(div.childNodes).to.have.length(0);
4448

4549
// mount a component passing div into the `attachTo` option
4650
const wrapper = mount(<Foo />, { attachTo: div });
51+
// or, mount a component passing div into the `hydrateIn` option
52+
const hydratedWrapper = mount(<Foo />, { hydrateIn: div });
4753

4854
// we can see now the component is rendered into the document
4955
expect(wrapper.find('.in-foo')).to.have.length(1);

packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ class ReactSixteenAdapter extends EnzymeAdapter {
175175
}
176176
createMountRenderer(options) {
177177
assertDomAvailable('mount');
178-
const domNode = options.attachTo || global.document.createElement('div');
178+
const { attachTo, hydrateIn } = options;
179+
const domNode = hydrateIn || attachTo || global.document.createElement('div');
179180
let instance = null;
180181
return {
181182
render(el, context, callback) {
@@ -189,7 +190,9 @@ class ReactSixteenAdapter extends EnzymeAdapter {
189190
};
190191
const ReactWrapperComponent = createMountWrapper(el, options);
191192
const wrappedEl = React.createElement(ReactWrapperComponent, wrapperProps);
192-
instance = ReactDOM.render(wrappedEl, domNode);
193+
instance = hydrateIn
194+
? ReactDOM.hydrate(wrappedEl, domNode)
195+
: ReactDOM.render(wrappedEl, domNode);
193196
if (typeof callback === 'function') {
194197
callback();
195198
}

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

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ describe('Adapter', () => {
6262
});
6363

6464
describeWithDOM('mounted render', () => {
65-
function hydratedTreeMatchesUnhydrated(element) {
65+
function hydratedTreeMatchesUnhydrated(element, hydrate = false) {
6666
const markup = renderToString(element);
6767
const dom = jsdom.jsdom(`<div id="root">${markup}</div>`);
6868

6969
const rendererA = adapter.createRenderer({
7070
mode: 'mount',
71-
attachTo: dom.querySelector('#root'),
71+
[hydrate ? 'hydrateIn' : 'attachTo']: dom.querySelector('#root'),
7272
});
7373

7474
rendererA.render(element);
@@ -89,32 +89,39 @@ describe('Adapter', () => {
8989
expect(prettyFormat(nodeA)).to.equal(prettyFormat(nodeB));
9090
}
9191

92-
it('hydrated trees match unhydrated trees', () => {
93-
class Bam extends React.Component {
94-
render() { return (<div>{this.props.children}</div>); }
95-
}
96-
class Foo extends React.Component {
97-
render() { return (<Bam>{this.props.children}</Bam>); }
98-
}
99-
class One extends React.Component {
100-
render() { return (<Foo><span><Foo /></span></Foo>); }
101-
}
102-
class Two extends React.Component {
103-
render() { return (<Foo><span>2</span></Foo>); }
104-
}
105-
class Three extends React.Component {
106-
render() { return (<Foo><span><div /></span></Foo>); }
107-
}
108-
class Four extends React.Component {
109-
render() { return (<Foo><span>{'some string'}4{'another string'}</span></Foo>); }
110-
}
92+
class BamBam extends React.Component {
93+
render() { return (<div>{this.props.children}</div>); }
94+
}
95+
class FooBar extends React.Component {
96+
render() { return (<BamBam>{this.props.children}</BamBam>); }
97+
}
98+
class One extends React.Component {
99+
render() { return (<FooBar><span><FooBar /></span></FooBar>); }
100+
}
101+
class Two extends React.Component {
102+
render() { return (<FooBar><span>2</span></FooBar>); }
103+
}
104+
class Three extends React.Component {
105+
render() { return (<FooBar><span><div /></span></FooBar>); }
106+
}
107+
class Four extends React.Component {
108+
render() { return (<FooBar><span>{'some string'}4{'another string'}</span></FooBar>); }
109+
}
111110

111+
it('hydrated trees match unhydrated trees', () => {
112112
hydratedTreeMatchesUnhydrated(<One />);
113113
hydratedTreeMatchesUnhydrated(<Two />);
114114
hydratedTreeMatchesUnhydrated(<Three />);
115115
hydratedTreeMatchesUnhydrated(<Four />);
116116
});
117117

118+
itIf(REACT16, 'works with ReactDOM.hydrate', () => {
119+
hydratedTreeMatchesUnhydrated(<One />, true);
120+
hydratedTreeMatchesUnhydrated(<Two />, true);
121+
hydratedTreeMatchesUnhydrated(<Three />, true);
122+
hydratedTreeMatchesUnhydrated(<Four />, true);
123+
});
124+
118125
it('treats mixed children correctly', () => {
119126
class Foo extends React.Component {
120127
render() {

packages/enzyme/src/Utils.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,25 @@ export function getAdapter(options = {}) {
2121
}
2222

2323
export function makeOptions(options) {
24+
const { attachTo, hydrateIn } = options;
25+
26+
if (attachTo && hydrateIn && attachTo !== hydrateIn) {
27+
throw new TypeError('If both the `attachTo` and `hydrateIn` options are provided, they must be === (for backwards compatibility)');
28+
}
29+
30+
// neither present: both undefined
31+
// only attachTo present: attachTo set, hydrateIn undefined
32+
// only hydrateIn present: both set to hydrateIn
33+
// both present (and ===, per above): both set to hydrateIn
34+
const mountTargets = {
35+
attachTo: hydrateIn || attachTo || undefined,
36+
hydrateIn: hydrateIn || undefined,
37+
};
38+
2439
return {
2540
...configuration.get(),
2641
...options,
42+
...mountTargets,
2743
};
2844
}
2945

0 commit comments

Comments
 (0)