From 5614f921902adfc9554d6b28b6ec220d94c9b9bf Mon Sep 17 00:00:00 2001 From: Leland Richardson Date: Sun, 20 Aug 2017 11:49:50 -0700 Subject: [PATCH] Make private properties more private and harder to use --- .../test/ReactWrapper-spec.jsx | 5 +- .../test/ShallowWrapper-spec.jsx | 6 +- packages/enzyme/src/ReactWrapper.jsx | 146 +++++++++------- packages/enzyme/src/ShallowWrapper.js | 159 +++++++++++------- packages/enzyme/src/Utils.js | 12 ++ 5 files changed, 204 insertions(+), 124 deletions(-) diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx index 95bcdef77..d62717747 100644 --- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx @@ -18,7 +18,7 @@ import { render, ReactWrapper, } from 'enzyme'; -import { ITERATOR_SYMBOL } from 'enzyme/build/Utils'; +import { ITERATOR_SYMBOL, sym } from 'enzyme/build/Utils'; import { REACT013, REACT014, REACT16, is } from './_helpers/version'; describeWithDOM('mount', () => { @@ -1037,6 +1037,7 @@ describeWithDOM('mount', () => { expect(setInvalidProps).to.throw(TypeError, similarException.message); }); + itIf(!REACT16, 'should call the callback when setProps has completed', () => { class Foo extends React.Component { render() { @@ -1050,7 +1051,7 @@ describeWithDOM('mount', () => { const wrapper = mount(); expect(wrapper.find('.foo').length).to.equal(1); - wrapper.renderer.batchedUpdates(() => { + wrapper[sym('__renderer__')].batchedUpdates(() => { wrapper.setProps({ id: 'bar', foo: 'bla' }, () => { expect(wrapper.find('.bar').length).to.equal(1); }); diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index 4ff2147a5..0a5e20d08 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -7,7 +7,7 @@ import sinon from 'sinon'; import { createClass } from './_helpers/react-compat'; import { shallow, render, ShallowWrapper } from 'enzyme'; import { describeIf, itIf, itWithData, generateEmptyRenderData } from './_helpers'; -import { ITERATOR_SYMBOL, withSetStateAllowed } from 'enzyme/build/Utils'; +import { ITERATOR_SYMBOL, withSetStateAllowed, sym } from 'enzyme/build/Utils'; import { REACT013, REACT014, REACT16, is } from './_helpers/version'; // The shallow renderer in react 16 does not yet support batched updates. When it does, @@ -4300,14 +4300,14 @@ describe('shallow', () => { it('works with a name', () => { const wrapper = shallow(
); wrapper.single('foo', (node) => { - expect(node).to.equal(wrapper.node); + expect(node).to.equal(wrapper[sym('__node__')]); }); }); it('works without a name', () => { const wrapper = shallow(
); wrapper.single((node) => { - expect(node).to.equal(wrapper.node); + expect(node).to.equal(wrapper[sym('__node__')]); }); }); }); diff --git a/packages/enzyme/src/ReactWrapper.jsx b/packages/enzyme/src/ReactWrapper.jsx index af3e22009..8cbc4a3d6 100644 --- a/packages/enzyme/src/ReactWrapper.jsx +++ b/packages/enzyme/src/ReactWrapper.jsx @@ -14,6 +14,8 @@ import { nodeEqual, nodeMatches, getAdapter, + sym, + privateSet, } from './Utils'; import { debugNodes, @@ -29,6 +31,14 @@ import { const noop = () => {}; +const NODE = sym('__node__'); +const NODES = sym('__nodes__'); +const COMPONENT = sym('__component__'); +const RENDERER = sym('__renderer__'); +const ROOT = sym('__root__'); +const OPTIONS = sym('__options__'); +const COMPLEX_SELECTOR = sym('__complexSelector__'); + /** * Finds all nodes in the current wrapper nodes' render trees that match the provided predicate * function. @@ -74,46 +84,47 @@ class ReactWrapper { } if (!root) { - this.renderer = getAdapter(options).createRenderer({ mode: 'mount', ...options }); + const renderer = getAdapter(options).createRenderer({ mode: 'mount', ...options }); + privateSet(this, RENDERER, renderer); const ReactWrapperComponent = createWrapperComponent(nodes, options); - this.renderer.render( + renderer.render( , ); - this.root = this; + privateSet(this, ROOT, this); const { component, node, - } = getFromRenderer(this.renderer); - this.component = component; - this.node = node; - this.nodes = [node]; + } = getFromRenderer(this[RENDERER]); + privateSet(this, COMPONENT, component); + privateSet(this, NODE, node); + privateSet(this, NODES, [node]); this.length = 1; } else { - this.renderer = root.renderer; - this.root = root; + privateSet(this, RENDERER, root[RENDERER]); + privateSet(this, ROOT, root); if (!nodes) { - this.node = null; - this.nodes = []; + privateSet(this, NODE, null); + privateSet(this, NODES, []); } else if (!Array.isArray(nodes)) { - this.node = nodes; - this.nodes = [nodes]; + privateSet(this, NODE, nodes); + privateSet(this, NODES, [nodes]); } else { - this.node = nodes[0]; - this.nodes = nodes; + privateSet(this, NODE, nodes[0]); + privateSet(this, NODES, nodes); } - this.length = this.nodes.length; - this.component = null; + this.length = this[NODES].length; + privateSet(this, COMPONENT, null); } - this.options = root ? root.options : options; - this.complexSelector = new ComplexSelector( + privateSet(this, OPTIONS, root ? root[OPTIONS] : options); + privateSet(this, COMPLEX_SELECTOR, new ComplexSelector( buildPredicate, findWhereUnwrapped, childrenOfNode, - ); + )); } /** @@ -127,7 +138,7 @@ class ReactWrapper { 'ReactWrapper::getNode() can only be called when wrapping one node', ); } - return this.nodes[0]; + return this[NODES][0]; } /** @@ -136,7 +147,7 @@ class ReactWrapper { * @return {Array} */ getNodesInternal() { - return this.nodes; + return this[NODES]; } /** @@ -150,7 +161,7 @@ class ReactWrapper { 'ReactWrapper::getElement() can only be called when wrapping one node', ); } - return getAdapter(this.options).nodeToElement(this.node); + return getAdapter(this[OPTIONS]).nodeToElement(this[NODE]); } /** @@ -159,7 +170,7 @@ class ReactWrapper { * @return {Array} */ getElements() { - return this.nodes.map(getAdapter(this.options).nodeToElement); + return this[NODES].map(getAdapter(this[OPTIONS]).nodeToElement); } // eslint-disable-next-line class-methods-use-this @@ -184,7 +195,7 @@ class ReactWrapper { * @returns {DOMComponent} */ getDOMNode() { - const adapter = getAdapter(this.options); + const adapter = getAdapter(this[OPTIONS]); return this.single('getDOMNode', n => adapter.nodeToHostNode(n)); } @@ -198,7 +209,7 @@ class ReactWrapper { * @returns {ReactWrapper} */ ref(refname) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::ref(refname) can only be called on the root'); } return this.instance().refs[refname]; @@ -221,7 +232,7 @@ class ReactWrapper { if (this.length !== 1) { throw new Error('ReactWrapper::instance() can only be called on single nodes'); } - return this.node.instance; + return this[NODE].instance; } /** @@ -233,17 +244,17 @@ class ReactWrapper { * @returns {ReactWrapper} */ update() { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::update() can only be called on the root'); } this.single('update', () => { const { component, node, - } = getFromRenderer(this.renderer); - this.component = component; - this.node = node; - this.nodes = [node]; + } = getFromRenderer(this[RENDERER]); + this[COMPONENT] = component; + this[NODE] = node; + this[NODES] = [node]; }); return this; } @@ -255,11 +266,11 @@ class ReactWrapper { * @returns {ReactWrapper} */ unmount() { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::unmount() can only be called on the root'); } this.single('unmount', () => { - this.component.setState({ mount: false }); + this[COMPONENT].setState({ mount: false }); this.update(); }); return this; @@ -272,11 +283,11 @@ class ReactWrapper { * @returns {ReactWrapper} */ mount() { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::mount() can only be called on the root'); } this.single('mount', () => { - this.component.setState({ mount: true }); + this[COMPONENT].setState({ mount: true }); this.update(); }); return this; @@ -297,13 +308,13 @@ class ReactWrapper { * @returns {ReactWrapper} */ setProps(props, callback = noop) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::setProps() can only be called on the root'); } if (typeof callback !== 'function') { throw new TypeError('ReactWrapper::setProps() expects a function as its second argument'); } - this.component.setChildProps(props, () => { + this[COMPONENT].setChildProps(props, () => { this.update(); callback(); }); @@ -324,7 +335,7 @@ class ReactWrapper { * @returns {ReactWrapper} */ setState(state, callback = noop) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::setState() can only be called on the root'); } if (typeof callback !== 'function') { @@ -347,16 +358,16 @@ class ReactWrapper { * @returns {ReactWrapper} */ setContext(context) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::setContext() can only be called on the root'); } - if (!this.options.context) { + if (!this[OPTIONS].context) { throw new Error( 'ShallowWrapper::setContext() can only be called on a wrapper that was originally passed ' + 'a context option', ); } - this.component.setChildContext(context); + this[COMPONENT].setChildContext(context); this.update(); return this; } @@ -476,7 +487,7 @@ class ReactWrapper { * @returns {ReactWrapper} */ find(selector) { - return this.complexSelector.find(selector, this); + return this[COMPLEX_SELECTOR].find(selector, this); } /** @@ -546,7 +557,7 @@ class ReactWrapper { * @returns {String} */ text() { - const adapter = getAdapter(this.options); + const adapter = getAdapter(this[OPTIONS]); return this.single('text', n => adapter.nodeToHostNode(n).textContent); } @@ -560,7 +571,7 @@ class ReactWrapper { html() { return this.single('html', (n) => { if (n === null) return null; - const adapter = getAdapter(this.options); + const adapter = getAdapter(this[OPTIONS]); const node = adapter.nodeToHostNode(n); return node === null ? null : node.outerHTML.replace(/\sdata-(reactid|reactroot)+="([^"]*)+"/g, ''); @@ -589,8 +600,8 @@ class ReactWrapper { */ simulate(event, mock = {}) { this.single('simulate', (n) => { - this.renderer.simulateEvent(n, event, mock); - this.root.update(); + this[RENDERER].simulateEvent(n, event, mock); + this[ROOT].update(); }); return this; } @@ -616,7 +627,7 @@ class ReactWrapper { * @returns {*} */ state(name) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::state() can only be called on the root'); } const _state = this.single('state', () => this.instance().state); @@ -636,7 +647,7 @@ class ReactWrapper { * @returns {*} */ context(name) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::context() can only be called on the root'); } const instance = this.single('context', () => this.instance()); @@ -682,7 +693,7 @@ class ReactWrapper { */ parents(selector) { const allParents = this.wrap( - this.single('parents', n => parentsOfNode(n, this.root.getNodeInternal())), + this.single('parents', n => parentsOfNode(n, this[ROOT].getNodeInternal())), ); return selector ? allParents.filter(selector) : allParents; } @@ -836,7 +847,7 @@ class ReactWrapper { * @returns {Boolean} */ some(selector) { - if (this.root === this) { + if (this[ROOT] === this) { throw new Error('ReactWrapper::some() can not be called on the root'); } const predicate = buildPredicate(selector); @@ -985,7 +996,7 @@ class ReactWrapper { * @param {ReactWrapper|ReactElement|Array} node * @returns {ReactWrapper} */ - wrap(node, root = this.root, ...args) { + wrap(node, root = this[ROOT], ...args) { if (node instanceof ReactWrapper) { return node; } @@ -1024,16 +1035,16 @@ class ReactWrapper { * not be doing anything with this wrapper after this method is called. */ detach() { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ReactWrapper::detach() can only be called on the root'); } - if (!this.options.attachTo) { + if (!this[OPTIONS].attachTo) { throw new Error( 'ReactWrapper::detach() can only be called on when the `attachTo` option was passed into ' + '`mount()`.', ); } - this.renderer.unmount(); + this[RENDERER].unmount(); } } @@ -1042,8 +1053,8 @@ if (ITERATOR_SYMBOL) { Object.defineProperty(ReactWrapper.prototype, ITERATOR_SYMBOL, { configurable: true, value: function iterator() { - const iter = this.nodes[ITERATOR_SYMBOL](); - const adapter = getAdapter(this.options); + const iter = this[NODES][ITERATOR_SYMBOL](); + const adapter = getAdapter(this[OPTIONS]); return { next() { const next = iter.next(); @@ -1060,4 +1071,25 @@ if (ITERATOR_SYMBOL) { }); } +function privateWarning(prop, extraMessage) { + Object.defineProperty(ReactWrapper.prototype, prop, { + get() { + throw new Error(` + Attempted to access ReactWrapper::${prop}, which was previously a private property on + Enzyme ReactWrapper instances, but is no longer and should not be relied upon. + ${extraMessage} + `); + }, + enumerable: false, + configurable: false, + }); +} + +privateWarning('node', 'Consider using the getElement() method instead.'); +privateWarning('nodes', 'Consider using the getElements() method instead.'); +privateWarning('renderer', ''); +privateWarning('root', ''); +privateWarning('options', ''); +privateWarning('complexSelector', ''); + export default ReactWrapper; diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js index 1c7cac30c..3044b1cde 100644 --- a/packages/enzyme/src/ShallowWrapper.js +++ b/packages/enzyme/src/ShallowWrapper.js @@ -16,6 +16,8 @@ import { isCustomComponentElement, ITERATOR_SYMBOL, getAdapter, + sym, + privateSet, } from './Utils'; import { debugNodes, @@ -30,6 +32,13 @@ import { buildPredicate, } from './RSTTraversal'; +const NODE = sym('__node__'); +const NODES = sym('__nodes__'); +const RENDERER = sym('__renderer__'); +const UNRENDERED = sym('__unrendered__'); +const ROOT = sym('__root__'); +const OPTIONS = sym('__options__'); +const COMPLEX_SELECTOR = sym('__complexSelector__'); /** * Finds all nodes in the current wrapper nodes' render trees that match the provided predicate * function. @@ -104,38 +113,43 @@ class ShallowWrapper { constructor(nodes, root, options = {}) { validateOptions(options); if (!root) { - this.root = this; - this.unrendered = nodes; - this.renderer = getAdapter(options).createRenderer({ mode: 'shallow', ...options }); - this.renderer.render(nodes, options.context); - const instance = this.renderer.getNode().instance; + privateSet(this, ROOT, this); + privateSet(this, UNRENDERED, nodes); + const renderer = getAdapter(options).createRenderer({ mode: 'shallow', ...options }); + privateSet(this, RENDERER, renderer); + this[RENDERER].render(nodes, options.context); + const instance = this[RENDERER].getNode().instance; if ( options.lifecycleExperimental && instance && typeof instance.componentDidMount === 'function' ) { - this.renderer.batchedUpdates(() => { + this[RENDERER].batchedUpdates(() => { instance.componentDidMount(); }); } - this.node = getRootNode(this.renderer.getNode()); - this.nodes = [this.node]; + privateSet(this, NODE, getRootNode(this[RENDERER].getNode())); + privateSet(this, NODES, [this[NODE]]); this.length = 1; } else { - this.root = root; - this.unrendered = null; - this.renderer = root.renderer; + privateSet(this, ROOT, root); + privateSet(this, UNRENDERED, null); + privateSet(this, RENDERER, root[RENDERER]); if (!Array.isArray(nodes)) { - this.node = nodes; - this.nodes = [nodes]; + privateSet(this, NODE, nodes); + privateSet(this, NODES, [nodes]); } else { - this.node = nodes[0]; - this.nodes = nodes; + privateSet(this, NODE, nodes[0]); + privateSet(this, NODES, nodes); } - this.length = this.nodes.length; + this.length = this[NODES].length; } - this.options = root ? root.options : options; - this.complexSelector = new ComplexSelector(buildPredicate, findWhereUnwrapped, childrenOfNode); + privateSet(this, OPTIONS, root ? root[OPTIONS] : options); + privateSet(this, COMPLEX_SELECTOR, new ComplexSelector( + buildPredicate, + findWhereUnwrapped, + childrenOfNode, + )); } getNodeInternal() { @@ -144,7 +158,7 @@ class ShallowWrapper { 'ShallowWrapper::getNode() can only be called when wrapping one node', ); } - return this.node; + return this[NODE]; } /** @@ -158,7 +172,7 @@ class ShallowWrapper { 'ShallowWrapper::getElement() can only be called when wrapping one node', ); } - return getAdapter(this.options).nodeToElement(this.node); + return getAdapter(this[OPTIONS]).nodeToElement(this[NODE]); } /** @@ -167,7 +181,7 @@ class ShallowWrapper { * @return {Array} */ getElements() { - return this.nodes.map(getAdapter(this.options).nodeToElement); + return this[NODES].map(getAdapter(this[OPTIONS]).nodeToElement); } // eslint-disable-next-line class-methods-use-this @@ -178,7 +192,7 @@ class ShallowWrapper { } getNodesInternal() { - return this.nodes; + return this[NODES]; } // eslint-disable-next-line class-methods-use-this @@ -202,10 +216,10 @@ class ShallowWrapper { * @returns {ReactComponent} */ instance() { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ShallowWrapper::instance() can only be called on the root'); } - return this.renderer.getNode().instance; + return this[RENDERER].getNode().instance; } /** @@ -217,12 +231,12 @@ class ShallowWrapper { * @returns {ShallowWrapper} */ update() { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ShallowWrapper::update() can only be called on the root'); } this.single('update', () => { - this.node = getRootNode(this.renderer.getNode()); - this.nodes = [this.node]; + this[NODE] = getRootNode(this[RENDERER].getNode()); + this[NODES] = [this[NODE]]; }); return this; } @@ -245,20 +259,20 @@ class ShallowWrapper { // this case, state will be undefined, but props/context will exist. const instance = this.instance() || {}; const state = instance.state; - const prevProps = instance.props || this.unrendered.props; - const prevContext = instance.context || this.options.context; + const prevProps = instance.props || this[UNRENDERED].props; + const prevContext = instance.context || this[OPTIONS].context; const nextProps = props || prevProps; const nextContext = context || prevContext; if (context) { - this.options = { ...this.options, context: nextContext }; + this[OPTIONS] = { ...this[OPTIONS], context: nextContext }; } - this.renderer.batchedUpdates(() => { + this[RENDERER].batchedUpdates(() => { let shouldRender = true; // dirty hack: // make sure that componentWillReceiveProps is called before shouldComponentUpdate let originalComponentWillReceiveProps; if ( - this.options.lifecycleExperimental && + this[OPTIONS].lifecycleExperimental && instance && typeof instance.componentWillReceiveProps === 'function' ) { @@ -269,7 +283,7 @@ class ShallowWrapper { // dirty hack: avoid calling shouldComponentUpdate twice let originalShouldComponentUpdate; if ( - this.options.lifecycleExperimental && + this[OPTIONS].lifecycleExperimental && instance && typeof instance.shouldComponentUpdate === 'function' ) { @@ -277,18 +291,18 @@ class ShallowWrapper { originalShouldComponentUpdate = instance.shouldComponentUpdate; } if (shouldRender) { - if (props) this.unrendered = React.cloneElement(this.unrendered, props); + if (props) this[UNRENDERED] = React.cloneElement(this[UNRENDERED], props); if (originalShouldComponentUpdate) { instance.shouldComponentUpdate = () => true; } - this.renderer.render(this.unrendered, nextContext); + this[RENDERER].render(this[UNRENDERED], nextContext); if (originalShouldComponentUpdate) { instance.shouldComponentUpdate = originalShouldComponentUpdate; } if ( - this.options.lifecycleExperimental && + this[OPTIONS].lifecycleExperimental && instance && typeof instance.componentDidUpdate === 'function' ) { @@ -322,7 +336,7 @@ class ShallowWrapper { * @returns {ShallowWrapper} */ setProps(props) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ShallowWrapper::setProps() can only be called on the root'); } return this.rerender(props); @@ -342,10 +356,10 @@ class ShallowWrapper { * @returns {ShallowWrapper} */ setState(state, callback = undefined) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ShallowWrapper::setState() can only be called on the root'); } - if (this.instance() === null || this.renderer.getNode().nodeType === 'function') { + if (this.instance() === null || this[RENDERER].getNode().nodeType === 'function') { throw new Error('ShallowWrapper::setState() can only be called on class components'); } this.single('setState', () => { @@ -367,10 +381,10 @@ class ShallowWrapper { * @returns {ShallowWrapper} */ setContext(context) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ShallowWrapper::setContext() can only be called on the root'); } - if (!this.options.context) { + if (!this[OPTIONS].context) { throw new Error( 'ShallowWrapper::setContext() can only be called on a wrapper that was originally passed ' + 'a context option', @@ -392,7 +406,7 @@ class ShallowWrapper { * @returns {Boolean} */ contains(nodeOrNodes) { - const adapter = getAdapter(this.options); + const adapter = getAdapter(this[OPTIONS]); if (!isReactElementAlike(nodeOrNodes, adapter)) { throw new Error( 'ShallowWrapper::contains() can only be called with ReactElement (or array of them), ' + @@ -526,7 +540,7 @@ class ShallowWrapper { * @returns {ShallowWrapper} */ find(selector) { - return this.complexSelector.find(selector, this); + return this[COMPLEX_SELECTOR].find(selector, this); } /** @@ -610,8 +624,8 @@ class ShallowWrapper { html() { return this.single('html', (n) => { if (this.type() === null) return null; - const adapter = getAdapter(this.options); - const renderer = adapter.createRenderer({ ...this.options, mode: 'string' }); + const adapter = getAdapter(this[OPTIONS]); + const renderer = adapter.createRenderer({ ...this[OPTIONS], mode: 'string' }); return renderer.render(adapter.nodeToElement(n)); }); } @@ -633,7 +647,7 @@ class ShallowWrapper { * @returns {ShallowWrapper} */ unmount() { - this.renderer.unmount(); + this[RENDERER].unmount(); return this; } @@ -647,8 +661,8 @@ class ShallowWrapper { */ simulate(event, ...args) { return this.single('simulate', (n) => { - this.renderer.simulateEvent(n, event, ...args); - this.root.update(); + this[RENDERER].simulateEvent(n, event, ...args); + this[ROOT].update(); }); } @@ -673,10 +687,10 @@ class ShallowWrapper { * @returns {*} */ state(name) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ShallowWrapper::state() can only be called on the root'); } - if (this.instance() === null || this.renderer.getNode().nodeType === 'function') { + if (this.instance() === null || this[RENDERER].getNode().nodeType === 'function') { throw new Error('ShallowWrapper::state() can only be called on class components'); } const _state = this.single('state', () => this.instance().state); @@ -696,10 +710,10 @@ class ShallowWrapper { * @returns {*} */ context(name) { - if (this.root !== this) { + if (this[ROOT] !== this) { throw new Error('ShallowWrapper::context() can only be called on the root'); } - if (!this.options.context) { + if (!this[OPTIONS].context) { throw new Error( 'ShallowWrapper::context() can only be called on a wrapper that was originally passed ' + 'a context option', @@ -750,7 +764,7 @@ class ShallowWrapper { */ parents(selector) { const allParents = this.wrap( - this.single('parents', n => parentsOfNode(n, this.root.node)), + this.single('parents', n => parentsOfNode(n, this[ROOT][NODE])), ); return selector ? allParents.filter(selector) : allParents; } @@ -783,7 +797,7 @@ class ShallowWrapper { */ shallow(options) { return this.single('shallow', n => this.wrap( - getAdapter(this.options).nodeToElement(n), null, options), + getAdapter(this[OPTIONS]).nodeToElement(n), null, options), ); } @@ -918,7 +932,7 @@ class ShallowWrapper { * @returns {Boolean} */ some(selector) { - if (this.root === this) { + if (this[ROOT] === this) { throw new Error('ShallowWrapper::some() can not be called on the root'); } const predicate = buildPredicate(selector); @@ -991,7 +1005,7 @@ class ShallowWrapper { * @returns {ReactElement} */ get(index) { - return getAdapter(this.options).nodeToElement(this.getNodesInternal()[index]); + return getAdapter(this[OPTIONS]).nodeToElement(this.getNodesInternal()[index]); } /** @@ -1068,7 +1082,7 @@ class ShallowWrapper { * @param node * @returns {ShallowWrapper} */ - wrap(node, root = this.root, ...args) { + wrap(node, root = this[ROOT], ...args) { if (node instanceof ShallowWrapper) { return node; } @@ -1105,17 +1119,17 @@ class ShallowWrapper { * @returns {ShallowWrapper} */ dive(options = {}) { - const adapter = getAdapter(this.options); + const adapter = getAdapter(this[OPTIONS]); const name = 'dive'; return this.single(name, (n) => { if (n && n.nodeType === 'host') { throw new TypeError(`ShallowWrapper::${name}() can not be called on Host Components`); } - const el = getAdapter(this.options).nodeToElement(n); + const el = getAdapter(this[OPTIONS]).nodeToElement(n); if (!isCustomComponentElement(el, adapter)) { throw new TypeError(`ShallowWrapper::${name}() can only be called on components`); } - return this.wrap(el, null, { ...this.options, ...options }); + return this.wrap(el, null, { ...this[OPTIONS], ...options }); }); } } @@ -1124,8 +1138,8 @@ if (ITERATOR_SYMBOL) { Object.defineProperty(ShallowWrapper.prototype, ITERATOR_SYMBOL, { configurable: true, value: function iterator() { - const iter = this.nodes[ITERATOR_SYMBOL](); - const adapter = getAdapter(this.options); + const iter = this[NODES][ITERATOR_SYMBOL](); + const adapter = getAdapter(this[OPTIONS]); return { next() { const next = iter.next(); @@ -1142,4 +1156,25 @@ if (ITERATOR_SYMBOL) { }); } +function privateWarning(prop, extraMessage) { + Object.defineProperty(ShallowWrapper.prototype, prop, { + get() { + throw new Error(` + Attempted to access ShallowWrapper::${prop}, which was previously a private property on + Enzyme ShallowWrapper instances, but is no longer and should not be relied upon. + ${extraMessage} + `); + }, + enumerable: false, + configurable: false, + }); +} + +privateWarning('node', 'Consider using the getElement() method instead.'); +privateWarning('nodes', 'Consider using the getElements() method instead.'); +privateWarning('renderer', ''); +privateWarning('root', ''); +privateWarning('options', ''); +privateWarning('complexSelector', ''); + export default ShallowWrapper; diff --git a/packages/enzyme/src/Utils.js b/packages/enzyme/src/Utils.js index 1e7243dd4..b021d812c 100644 --- a/packages/enzyme/src/Utils.js +++ b/packages/enzyme/src/Utils.js @@ -362,3 +362,15 @@ export function displayNameOfNode(node) { return type.displayName || (typeof type === 'function' ? functionName(type) : type.name || type); } + +export function sym(s) { + return typeof Symbol === 'function' ? Symbol.for(`enzyme.${s}`) : s; +} + +export function privateSet(obj, prop, value) { + Object.defineProperty(obj, prop, { + value, + enumerable: false, + writable: true, + }); +}