rails-assets-enzyme 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/lib/rails/assets/enzyme.rb +9 -0
- data/lib/rails/assets/enzyme/version.rb +7 -0
- data/vendor/assets/javascripts/Debug.js +133 -0
- data/vendor/assets/javascripts/MountedTraversal.js +305 -0
- data/vendor/assets/javascripts/ReactWrapper.js +777 -0
- data/vendor/assets/javascripts/ReactWrapperComponent.jsx +91 -0
- data/vendor/assets/javascripts/ShallowTraversal.js +173 -0
- data/vendor/assets/javascripts/ShallowWrapper.js +744 -0
- data/vendor/assets/javascripts/Utils.js +266 -0
- data/vendor/assets/javascripts/index.js +14 -0
- data/vendor/assets/javascripts/mount.js +11 -0
- data/vendor/assets/javascripts/react-compat.js +166 -0
- data/vendor/assets/javascripts/render.js +19 -0
- data/vendor/assets/javascripts/shallow.js +11 -0
- data/vendor/assets/javascripts/version.js +5 -0
- metadata +89 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
import React, { PropTypes } from 'react';
|
2
|
+
import objectAssign from 'object.assign';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* This is a utility component to wrap around the nodes we are
|
6
|
+
* passing in to `mount()`. Theoretically, you could do everything
|
7
|
+
* we are doing without this, but this makes it easier since
|
8
|
+
* `renderIntoDocument()` doesn't really pass back a reference to
|
9
|
+
* the DOM node it rendered to, so we can't really "re-render" to
|
10
|
+
* pass new props in.
|
11
|
+
*/
|
12
|
+
export default function createWrapperComponent(node, options = {}) {
|
13
|
+
const spec = {
|
14
|
+
|
15
|
+
propTypes: {
|
16
|
+
Component: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).isRequired,
|
17
|
+
props: PropTypes.object.isRequired,
|
18
|
+
context: PropTypes.object,
|
19
|
+
},
|
20
|
+
|
21
|
+
getDefaultProps() {
|
22
|
+
return {
|
23
|
+
context: null,
|
24
|
+
};
|
25
|
+
},
|
26
|
+
|
27
|
+
getInitialState() {
|
28
|
+
return {
|
29
|
+
mount: true,
|
30
|
+
props: this.props.props,
|
31
|
+
context: this.props.context,
|
32
|
+
};
|
33
|
+
},
|
34
|
+
|
35
|
+
setChildProps(newProps) {
|
36
|
+
const props = objectAssign({}, this.state.props, newProps);
|
37
|
+
this.setState({ props });
|
38
|
+
},
|
39
|
+
|
40
|
+
setChildContext(context) {
|
41
|
+
return new Promise(resolve => this.setState({ context }, resolve));
|
42
|
+
},
|
43
|
+
|
44
|
+
getInstance() {
|
45
|
+
const component = this._reactInternalInstance._renderedComponent;
|
46
|
+
const inst = component.getPublicInstance();
|
47
|
+
if (inst === null) {
|
48
|
+
return component._instance;
|
49
|
+
}
|
50
|
+
return inst;
|
51
|
+
},
|
52
|
+
|
53
|
+
getWrappedComponent() {
|
54
|
+
const component = this._reactInternalInstance._renderedComponent;
|
55
|
+
const inst = component.getPublicInstance();
|
56
|
+
if (inst === null) {
|
57
|
+
return component;
|
58
|
+
}
|
59
|
+
return inst;
|
60
|
+
},
|
61
|
+
|
62
|
+
render() {
|
63
|
+
const { Component } = this.props;
|
64
|
+
const { mount, props } = this.state;
|
65
|
+
if (!mount) return null;
|
66
|
+
return (
|
67
|
+
<Component {...props} />
|
68
|
+
);
|
69
|
+
},
|
70
|
+
};
|
71
|
+
|
72
|
+
if (options.context && (node.type.contextTypes || options.childContextTypes)) {
|
73
|
+
// For full rendering, we are using this wrapper component to provide context if it is
|
74
|
+
// specified in both the options AND the child component defines `contextTypes` statically
|
75
|
+
// OR the merged context types for all children (the node component or deeper children) are
|
76
|
+
// specified in options parameter under childContextTypes.
|
77
|
+
// In that case, we define both a `getChildContext()` function and a `childContextTypes` prop.
|
78
|
+
const childContextTypes = node.type.contextTypes || {};
|
79
|
+
if (options.childContextTypes) {
|
80
|
+
objectAssign(childContextTypes, options.childContextTypes);
|
81
|
+
}
|
82
|
+
objectAssign(spec, {
|
83
|
+
childContextTypes,
|
84
|
+
getChildContext() {
|
85
|
+
return this.state.context;
|
86
|
+
},
|
87
|
+
});
|
88
|
+
}
|
89
|
+
|
90
|
+
return React.createClass(spec);
|
91
|
+
}
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import isEmpty from 'lodash/isEmpty';
|
3
|
+
import isSubset from 'is-subset';
|
4
|
+
import {
|
5
|
+
coercePropValue,
|
6
|
+
propsOfNode,
|
7
|
+
isSimpleSelector,
|
8
|
+
splitSelector,
|
9
|
+
selectorError,
|
10
|
+
isCompoundSelector,
|
11
|
+
selectorType,
|
12
|
+
AND,
|
13
|
+
SELECTOR,
|
14
|
+
nodeHasType,
|
15
|
+
} from './Utils';
|
16
|
+
|
17
|
+
|
18
|
+
export function childrenOfNode(node) {
|
19
|
+
if (!node) return [];
|
20
|
+
const maybeArray = propsOfNode(node).children;
|
21
|
+
const result = [];
|
22
|
+
React.Children.forEach(maybeArray, child => {
|
23
|
+
if (child !== null && child !== false && typeof child !== 'undefined') {
|
24
|
+
result.push(child);
|
25
|
+
}
|
26
|
+
});
|
27
|
+
return result;
|
28
|
+
}
|
29
|
+
|
30
|
+
export function hasClassName(node, className) {
|
31
|
+
let classes = propsOfNode(node).className || '';
|
32
|
+
classes = classes.replace(/\s/g, ' ');
|
33
|
+
return ` ${classes} `.indexOf(` ${className} `) > -1;
|
34
|
+
}
|
35
|
+
|
36
|
+
export function treeForEach(tree, fn) {
|
37
|
+
if (tree !== null && tree !== false && typeof tree !== 'undefined') {
|
38
|
+
fn(tree);
|
39
|
+
}
|
40
|
+
childrenOfNode(tree).forEach(node => treeForEach(node, fn));
|
41
|
+
}
|
42
|
+
|
43
|
+
export function treeFilter(tree, fn) {
|
44
|
+
const results = [];
|
45
|
+
treeForEach(tree, node => {
|
46
|
+
if (fn(node)) {
|
47
|
+
results.push(node);
|
48
|
+
}
|
49
|
+
});
|
50
|
+
return results;
|
51
|
+
}
|
52
|
+
|
53
|
+
function pathFilter(path, fn) {
|
54
|
+
return path.filter(tree => treeFilter(tree, fn).length !== 0);
|
55
|
+
}
|
56
|
+
|
57
|
+
export function pathToNode(node, root) {
|
58
|
+
const queue = [root];
|
59
|
+
const path = [];
|
60
|
+
|
61
|
+
const hasNode = (testNode) => node === testNode;
|
62
|
+
|
63
|
+
while (queue.length) {
|
64
|
+
const current = queue.pop();
|
65
|
+
const children = childrenOfNode(current);
|
66
|
+
if (current === node) return pathFilter(path, hasNode);
|
67
|
+
|
68
|
+
path.push(current);
|
69
|
+
|
70
|
+
if (children.length === 0) {
|
71
|
+
// leaf node. if it isn't the node we are looking for, we pop.
|
72
|
+
path.pop();
|
73
|
+
}
|
74
|
+
queue.push.apply(queue, children);
|
75
|
+
}
|
76
|
+
|
77
|
+
return null;
|
78
|
+
}
|
79
|
+
|
80
|
+
export function parentsOfNode(node, root) {
|
81
|
+
return pathToNode(node, root).reverse();
|
82
|
+
}
|
83
|
+
|
84
|
+
export function nodeHasId(node, id) {
|
85
|
+
return propsOfNode(node).id === id;
|
86
|
+
}
|
87
|
+
|
88
|
+
|
89
|
+
export function nodeHasProperty(node, propKey, stringifiedPropValue) {
|
90
|
+
const nodeProps = propsOfNode(node);
|
91
|
+
const propValue = coercePropValue(propKey, stringifiedPropValue);
|
92
|
+
const descriptor = Object.getOwnPropertyDescriptor(nodeProps, propKey);
|
93
|
+
if (descriptor && descriptor.get) {
|
94
|
+
return false;
|
95
|
+
}
|
96
|
+
const nodePropValue = nodeProps[propKey];
|
97
|
+
|
98
|
+
if (nodePropValue === undefined) {
|
99
|
+
return false;
|
100
|
+
}
|
101
|
+
|
102
|
+
if (propValue) {
|
103
|
+
return nodePropValue === propValue;
|
104
|
+
}
|
105
|
+
|
106
|
+
return nodeProps.hasOwnProperty(propKey);
|
107
|
+
}
|
108
|
+
|
109
|
+
export function nodeMatchesObjectProps(node, props) {
|
110
|
+
return isSubset(propsOfNode(node), props);
|
111
|
+
}
|
112
|
+
|
113
|
+
export function buildPredicate(selector) {
|
114
|
+
switch (typeof selector) {
|
115
|
+
case 'function':
|
116
|
+
// selector is a component constructor
|
117
|
+
return node => node && node.type === selector;
|
118
|
+
|
119
|
+
case 'string':
|
120
|
+
if (!isSimpleSelector(selector)) {
|
121
|
+
throw selectorError(selector);
|
122
|
+
}
|
123
|
+
if (isCompoundSelector.test(selector)) {
|
124
|
+
return AND(splitSelector(selector).map(buildPredicate));
|
125
|
+
}
|
126
|
+
|
127
|
+
switch (selectorType(selector)) {
|
128
|
+
case SELECTOR.CLASS_TYPE:
|
129
|
+
return node => hasClassName(node, selector.substr(1));
|
130
|
+
|
131
|
+
case SELECTOR.ID_TYPE:
|
132
|
+
return node => nodeHasId(node, selector.substr(1));
|
133
|
+
|
134
|
+
case SELECTOR.PROP_TYPE: {
|
135
|
+
const propKey = selector.split(/\[([a-zA-Z\-]*?)(=|\])/)[1];
|
136
|
+
const propValue = selector.split(/=(.*?)\]/)[1];
|
137
|
+
|
138
|
+
return node => nodeHasProperty(node, propKey, propValue);
|
139
|
+
}
|
140
|
+
default:
|
141
|
+
// selector is a string. match to DOM tag or constructor displayName
|
142
|
+
return node => nodeHasType(node, selector);
|
143
|
+
}
|
144
|
+
|
145
|
+
case 'object':
|
146
|
+
if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) {
|
147
|
+
return node => nodeMatchesObjectProps(node, selector);
|
148
|
+
}
|
149
|
+
throw new TypeError(
|
150
|
+
'Enzyme::Selector does not support an array, null, or empty object as a selector'
|
151
|
+
);
|
152
|
+
|
153
|
+
default:
|
154
|
+
throw new TypeError('Enzyme::Selector expects a string, object, or Component Constructor');
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
|
159
|
+
export function getTextFromNode(node) {
|
160
|
+
if (node === null || node === undefined) {
|
161
|
+
return '';
|
162
|
+
}
|
163
|
+
|
164
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
165
|
+
return String(node);
|
166
|
+
}
|
167
|
+
|
168
|
+
if (node.type && typeof node.type === 'function') {
|
169
|
+
return `<${node.type.displayName || node.type.name} />`;
|
170
|
+
}
|
171
|
+
|
172
|
+
return childrenOfNode(node).map(getTextFromNode).join('').replace(/\s+/, ' ');
|
173
|
+
}
|
@@ -0,0 +1,744 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import flatten from 'lodash/flatten';
|
3
|
+
import unique from 'lodash/uniq';
|
4
|
+
import compact from 'lodash/compact';
|
5
|
+
import cheerio from 'cheerio';
|
6
|
+
import {
|
7
|
+
nodeEqual,
|
8
|
+
containsChildrenSubArray,
|
9
|
+
propFromEvent,
|
10
|
+
withSetStateAllowed,
|
11
|
+
propsOfNode,
|
12
|
+
typeOfNode,
|
13
|
+
} from './Utils';
|
14
|
+
import {
|
15
|
+
debugNodes,
|
16
|
+
} from './Debug';
|
17
|
+
import {
|
18
|
+
getTextFromNode,
|
19
|
+
hasClassName,
|
20
|
+
childrenOfNode,
|
21
|
+
parentsOfNode,
|
22
|
+
treeFilter,
|
23
|
+
buildPredicate,
|
24
|
+
} from './ShallowTraversal';
|
25
|
+
import {
|
26
|
+
createShallowRenderer,
|
27
|
+
renderToStaticMarkup,
|
28
|
+
} from './react-compat';
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
|
32
|
+
* function.
|
33
|
+
*
|
34
|
+
* @param {ShallowWrapper} wrapper
|
35
|
+
* @param {Function} predicate
|
36
|
+
* @returns {ShallowWrapper}
|
37
|
+
*/
|
38
|
+
function findWhereUnwrapped(wrapper, predicate) {
|
39
|
+
return wrapper.flatMap(n => treeFilter(n.node, predicate));
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Returns a new wrapper instance with only the nodes of the current wrapper instance that match
|
44
|
+
* the provided predicate function.
|
45
|
+
*
|
46
|
+
* @param {ShallowWrapper} wrapper
|
47
|
+
* @param {Function} predicate
|
48
|
+
* @returns {ShallowWrapper}
|
49
|
+
*/
|
50
|
+
function filterWhereUnwrapped(wrapper, predicate) {
|
51
|
+
return wrapper.wrap(compact(wrapper.nodes.filter(predicate)));
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* @class ShallowWrapper
|
56
|
+
*/
|
57
|
+
export default class ShallowWrapper {
|
58
|
+
|
59
|
+
constructor(nodes, root, options = {}) {
|
60
|
+
if (!root) {
|
61
|
+
this.root = this;
|
62
|
+
this.unrendered = nodes;
|
63
|
+
this.renderer = createShallowRenderer();
|
64
|
+
this.renderer.render(nodes, options.context);
|
65
|
+
this.node = this.renderer.getRenderOutput();
|
66
|
+
this.nodes = [this.node];
|
67
|
+
this.length = 1;
|
68
|
+
} else {
|
69
|
+
this.root = root;
|
70
|
+
this.unrendered = null;
|
71
|
+
this.renderer = null;
|
72
|
+
if (!Array.isArray(nodes)) {
|
73
|
+
this.node = nodes;
|
74
|
+
this.nodes = [nodes];
|
75
|
+
} else {
|
76
|
+
this.node = nodes[0];
|
77
|
+
this.nodes = nodes;
|
78
|
+
}
|
79
|
+
this.length = this.nodes.length;
|
80
|
+
}
|
81
|
+
this.options = options;
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Gets the instance of the component being rendered as the root node passed into `shallow()`.
|
86
|
+
*
|
87
|
+
* NOTE: can only be called on a wrapper instance that is also the root instance.
|
88
|
+
*
|
89
|
+
* Example:
|
90
|
+
* ```
|
91
|
+
* const wrapper = shallow(<MyComponent />);
|
92
|
+
* const inst = wrapper.instance();
|
93
|
+
* expect(inst).to.be.instanceOf(MyComponent);
|
94
|
+
* ```
|
95
|
+
* @returns {ReactComponent}
|
96
|
+
*/
|
97
|
+
instance() {
|
98
|
+
return this.renderer._instance._instance;
|
99
|
+
}
|
100
|
+
|
101
|
+
/**
|
102
|
+
* Forces a re-render. Useful to run before checking the render output if something external
|
103
|
+
* may be updating the state of the component somewhere.
|
104
|
+
*
|
105
|
+
* NOTE: can only be called on a wrapper instance that is also the root instance.
|
106
|
+
*
|
107
|
+
* @returns {ShallowWrapper}
|
108
|
+
*/
|
109
|
+
update() {
|
110
|
+
if (this.root !== this) {
|
111
|
+
throw new Error('ShallowWrapper::update() can only be called on the root');
|
112
|
+
}
|
113
|
+
this.single(() => {
|
114
|
+
this.node = this.renderer.getRenderOutput();
|
115
|
+
this.nodes = [this.node];
|
116
|
+
});
|
117
|
+
return this;
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* A method that sets the props of the root component, and re-renders. Useful for when you are
|
122
|
+
* wanting to test how the component behaves over time with changing props. Calling this, for
|
123
|
+
* instance, will call the `componentWillReceiveProps` lifecycle method.
|
124
|
+
*
|
125
|
+
* Similar to `setState`, this method accepts a props object and will merge it in with the already
|
126
|
+
* existing props.
|
127
|
+
*
|
128
|
+
* NOTE: can only be called on a wrapper instance that is also the root instance.
|
129
|
+
*
|
130
|
+
* @param {Object} props object
|
131
|
+
* @returns {ShallowWrapper}
|
132
|
+
*/
|
133
|
+
setProps(props) {
|
134
|
+
if (this.root !== this) {
|
135
|
+
throw new Error('ShallowWrapper::setProps() can only be called on the root');
|
136
|
+
}
|
137
|
+
this.single(() => {
|
138
|
+
withSetStateAllowed(() => {
|
139
|
+
this.unrendered = React.cloneElement(this.unrendered, props);
|
140
|
+
this.renderer.render(this.unrendered, this.options.context);
|
141
|
+
this.update();
|
142
|
+
});
|
143
|
+
});
|
144
|
+
return this;
|
145
|
+
}
|
146
|
+
|
147
|
+
/**
|
148
|
+
* A method to invoke `setState` on the root component instance similar to how you might in the
|
149
|
+
* definition of the component, and re-renders. This method is useful for testing your component
|
150
|
+
* in hard to achieve states, however should be used sparingly. If possible, you should utilize
|
151
|
+
* your component's external API in order to get it into whatever state you want to test, in order
|
152
|
+
* to be as accurate of a test as possible. This is not always practical, however.
|
153
|
+
*
|
154
|
+
* NOTE: can only be called on a wrapper instance that is also the root instance.
|
155
|
+
*
|
156
|
+
* @param {Object} state to merge
|
157
|
+
* @returns {ShallowWrapper}
|
158
|
+
*/
|
159
|
+
setState(state) {
|
160
|
+
if (this.root !== this) {
|
161
|
+
throw new Error('ShallowWrapper::setState() can only be called on the root');
|
162
|
+
}
|
163
|
+
this.single(() => {
|
164
|
+
withSetStateAllowed(() => {
|
165
|
+
this.instance().setState(state);
|
166
|
+
this.update();
|
167
|
+
});
|
168
|
+
});
|
169
|
+
return this;
|
170
|
+
}
|
171
|
+
|
172
|
+
/**
|
173
|
+
* A method that sets the context of the root component, and re-renders. Useful for when you are
|
174
|
+
* wanting to test how the component behaves over time with changing contexts.
|
175
|
+
*
|
176
|
+
* NOTE: can only be called on a wrapper instance that is also the root instance.
|
177
|
+
*
|
178
|
+
* @param {Object} context object
|
179
|
+
* @returns {ShallowWrapper}
|
180
|
+
*/
|
181
|
+
setContext(context) {
|
182
|
+
if (this.root !== this) {
|
183
|
+
throw new Error('ShallowWrapper::setContext() can only be called on the root');
|
184
|
+
}
|
185
|
+
if (!this.options.context) {
|
186
|
+
throw new Error(
|
187
|
+
'ShallowWrapper::setContext() can only be called on a wrapper that was originally passed ' +
|
188
|
+
'a context option'
|
189
|
+
);
|
190
|
+
}
|
191
|
+
this.renderer.render(this.unrendered, context);
|
192
|
+
this.update();
|
193
|
+
return this;
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Whether or not a given react element exists in the shallow render tree.
|
198
|
+
*
|
199
|
+
* Example:
|
200
|
+
* ```
|
201
|
+
* const wrapper = shallow(<MyComponent />);
|
202
|
+
* expect(wrapper.contains(<div className="foo bar" />)).to.equal(true);
|
203
|
+
* ```
|
204
|
+
*
|
205
|
+
* @param {ReactElement|Array<ReactElement>} nodeOrNodes
|
206
|
+
* @returns {Boolean}
|
207
|
+
*/
|
208
|
+
contains(nodeOrNodes) {
|
209
|
+
const predicate = Array.isArray(nodeOrNodes)
|
210
|
+
? other => containsChildrenSubArray(nodeEqual, other, nodeOrNodes)
|
211
|
+
: other => nodeEqual(nodeOrNodes, other);
|
212
|
+
|
213
|
+
return findWhereUnwrapped(this, predicate).length > 0;
|
214
|
+
}
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Whether or not a given react element exists in the shallow render tree.
|
218
|
+
*
|
219
|
+
* Example:
|
220
|
+
* ```
|
221
|
+
* const wrapper = shallow(<MyComponent />);
|
222
|
+
* expect(wrapper.contains(<div className="foo bar" />)).to.equal(true);
|
223
|
+
* ```
|
224
|
+
*
|
225
|
+
* @param {ReactElement} node
|
226
|
+
* @returns {Boolean}
|
227
|
+
*/
|
228
|
+
equals(node) {
|
229
|
+
return this.single(() => nodeEqual(this.node, node));
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* Finds every node in the render tree of the current wrapper that matches the provided selector.
|
234
|
+
*
|
235
|
+
* @param {String|Function} selector
|
236
|
+
* @returns {ShallowWrapper}
|
237
|
+
*/
|
238
|
+
find(selector) {
|
239
|
+
const predicate = buildPredicate(selector);
|
240
|
+
return findWhereUnwrapped(this, predicate);
|
241
|
+
}
|
242
|
+
|
243
|
+
/**
|
244
|
+
* Returns whether or not current node matches a provided selector.
|
245
|
+
*
|
246
|
+
* NOTE: can only be called on a wrapper of a single node.
|
247
|
+
*
|
248
|
+
* @param {String|Function} selector
|
249
|
+
* @returns {boolean}
|
250
|
+
*/
|
251
|
+
is(selector) {
|
252
|
+
const predicate = buildPredicate(selector);
|
253
|
+
return this.single(predicate);
|
254
|
+
}
|
255
|
+
|
256
|
+
/**
|
257
|
+
* Returns a new wrapper instance with only the nodes of the current wrapper instance that match
|
258
|
+
* the provided predicate function. The predicate should receive a wrapped node as its first
|
259
|
+
* argument.
|
260
|
+
*
|
261
|
+
* @param {Function} predicate
|
262
|
+
* @returns {ShallowWrapper}
|
263
|
+
*/
|
264
|
+
filterWhere(predicate) {
|
265
|
+
return filterWhereUnwrapped(this, n => predicate(this.wrap(n)));
|
266
|
+
}
|
267
|
+
|
268
|
+
/**
|
269
|
+
* Returns a new wrapper instance with only the nodes of the current wrapper instance that match
|
270
|
+
* the provided selector.
|
271
|
+
*
|
272
|
+
* @param {String|Function} selector
|
273
|
+
* @returns {ShallowWrapper}
|
274
|
+
*/
|
275
|
+
filter(selector) {
|
276
|
+
const predicate = buildPredicate(selector);
|
277
|
+
return filterWhereUnwrapped(this, predicate);
|
278
|
+
}
|
279
|
+
|
280
|
+
/**
|
281
|
+
* Returns a new wrapper instance with only the nodes of the current wrapper that did not match
|
282
|
+
* the provided selector. Essentially the inverse of `filter`.
|
283
|
+
*
|
284
|
+
* @param {String|Function} selector
|
285
|
+
* @returns {ShallowWrapper}
|
286
|
+
*/
|
287
|
+
not(selector) {
|
288
|
+
const predicate = buildPredicate(selector);
|
289
|
+
return filterWhereUnwrapped(this, n => !predicate(n));
|
290
|
+
}
|
291
|
+
|
292
|
+
/**
|
293
|
+
* Returns a string of the rendered text of the current render tree. This function should be
|
294
|
+
* looked at with skepticism if being used to test what the actual HTML output of the component
|
295
|
+
* will be. If that is what you would like to test, use enzyme's `render` function instead.
|
296
|
+
*
|
297
|
+
* NOTE: can only be called on a wrapper of a single node.
|
298
|
+
*
|
299
|
+
* @returns {String}
|
300
|
+
*/
|
301
|
+
text() {
|
302
|
+
return this.single(getTextFromNode);
|
303
|
+
}
|
304
|
+
|
305
|
+
/**
|
306
|
+
* Returns the HTML of the node.
|
307
|
+
*
|
308
|
+
* NOTE: can only be called on a wrapper of a single node.
|
309
|
+
*
|
310
|
+
* @returns {String}
|
311
|
+
*/
|
312
|
+
html() {
|
313
|
+
return this.single(n => {
|
314
|
+
// NOTE: splitting this into two statements is required to make the linter happy.
|
315
|
+
const isNull = this.type() === null;
|
316
|
+
return isNull ? null : renderToStaticMarkup(n);
|
317
|
+
});
|
318
|
+
}
|
319
|
+
|
320
|
+
/**
|
321
|
+
* Returns the current node rendered to HTML and wrapped in a CheerioWrapper.
|
322
|
+
*
|
323
|
+
* NOTE: can only be called on a wrapper of a single node.
|
324
|
+
*
|
325
|
+
* @returns {CheerioWrapper}
|
326
|
+
*/
|
327
|
+
render() {
|
328
|
+
return this.type() === null ? cheerio() : cheerio.load(this.html()).root();
|
329
|
+
}
|
330
|
+
|
331
|
+
/**
|
332
|
+
* A method that unmounts the component. This can be used to simulate a component going through
|
333
|
+
* and unmount/mount lifecycle.
|
334
|
+
* @returns {ShallowWrapper}
|
335
|
+
*/
|
336
|
+
unmount() {
|
337
|
+
this.renderer.unmount();
|
338
|
+
return this;
|
339
|
+
}
|
340
|
+
|
341
|
+
/**
|
342
|
+
* Used to simulate events. Pass an eventname and (optionally) event arguments. This method of
|
343
|
+
* testing events should be met with some skepticism.
|
344
|
+
*
|
345
|
+
* @param {String} event
|
346
|
+
* @param {Array} args
|
347
|
+
* @returns {ShallowWrapper}
|
348
|
+
*/
|
349
|
+
simulate(event, ...args) {
|
350
|
+
const handler = this.prop(propFromEvent(event));
|
351
|
+
if (handler) {
|
352
|
+
withSetStateAllowed(() => {
|
353
|
+
// TODO(lmr): create/use synthetic events
|
354
|
+
// TODO(lmr): emulate React's event propagation
|
355
|
+
handler(...args);
|
356
|
+
this.root.update();
|
357
|
+
});
|
358
|
+
}
|
359
|
+
return this;
|
360
|
+
}
|
361
|
+
|
362
|
+
/**
|
363
|
+
* Returns the props hash for the root node of the wrapper.
|
364
|
+
*
|
365
|
+
* NOTE: can only be called on a wrapper of a single node.
|
366
|
+
*
|
367
|
+
* @returns {Object}
|
368
|
+
*/
|
369
|
+
props() {
|
370
|
+
return this.single(propsOfNode);
|
371
|
+
}
|
372
|
+
|
373
|
+
/**
|
374
|
+
* Returns the state hash for the root node of the wrapper. Optionally pass in a prop name and it
|
375
|
+
* will return just that value.
|
376
|
+
*
|
377
|
+
* NOTE: can only be called on a wrapper of a single node.
|
378
|
+
*
|
379
|
+
* @param {String} name (optional)
|
380
|
+
* @returns {*}
|
381
|
+
*/
|
382
|
+
state(name) {
|
383
|
+
if (this.root !== this) {
|
384
|
+
throw new Error('ShallowWrapper::state() can only be called on the root');
|
385
|
+
}
|
386
|
+
const _state = this.single(() => this.instance().state);
|
387
|
+
if (name !== undefined) {
|
388
|
+
return _state[name];
|
389
|
+
}
|
390
|
+
return _state;
|
391
|
+
}
|
392
|
+
|
393
|
+
/**
|
394
|
+
* Returns the context hash for the root node of the wrapper.
|
395
|
+
* Optionally pass in a prop name and it will return just that value.
|
396
|
+
*
|
397
|
+
* NOTE: can only be called on a wrapper of a single node.
|
398
|
+
*
|
399
|
+
* @param {String} name (optional)
|
400
|
+
* @returns {*}
|
401
|
+
*/
|
402
|
+
context(name) {
|
403
|
+
if (this.root !== this) {
|
404
|
+
throw new Error('ShallowWrapper::context() can only be called on the root');
|
405
|
+
}
|
406
|
+
const _context = this.single(() => this.instance().context);
|
407
|
+
if (name) {
|
408
|
+
return _context[name];
|
409
|
+
}
|
410
|
+
return _context;
|
411
|
+
}
|
412
|
+
|
413
|
+
/**
|
414
|
+
* Returns a new wrapper with all of the children of the current wrapper.
|
415
|
+
*
|
416
|
+
* @param {String|Function} [selector]
|
417
|
+
* @returns {ShallowWrapper}
|
418
|
+
*/
|
419
|
+
children(selector) {
|
420
|
+
const allChildren = this.flatMap(n => childrenOfNode(n.node));
|
421
|
+
return selector ? allChildren.filter(selector) : allChildren;
|
422
|
+
}
|
423
|
+
|
424
|
+
/**
|
425
|
+
* Returns a new wrapper with a specific child
|
426
|
+
*
|
427
|
+
* @param {Number} [index]
|
428
|
+
* @returns {ShallowWrapper}
|
429
|
+
*/
|
430
|
+
childAt(index) {
|
431
|
+
return this.single(() => this.children().at(index));
|
432
|
+
}
|
433
|
+
|
434
|
+
/**
|
435
|
+
* Returns a wrapper around all of the parents/ancestors of the wrapper. Does not include the node
|
436
|
+
* in the current wrapper.
|
437
|
+
*
|
438
|
+
* NOTE: can only be called on a wrapper of a single node.
|
439
|
+
*
|
440
|
+
* @param {String|Function} [selector]
|
441
|
+
* @returns {ShallowWrapper}
|
442
|
+
*/
|
443
|
+
parents(selector) {
|
444
|
+
const allParents = this.wrap(this.single(n => parentsOfNode(n, this.root.node)));
|
445
|
+
return selector ? allParents.filter(selector) : allParents;
|
446
|
+
}
|
447
|
+
|
448
|
+
/**
|
449
|
+
* Returns a wrapper around the immediate parent of the current node.
|
450
|
+
*
|
451
|
+
* @returns {ShallowWrapper}
|
452
|
+
*/
|
453
|
+
parent() {
|
454
|
+
return this.flatMap(n => [n.parents().get(0)]);
|
455
|
+
}
|
456
|
+
|
457
|
+
/**
|
458
|
+
*
|
459
|
+
* @param {String|Function} selector
|
460
|
+
* @returns {ShallowWrapper}
|
461
|
+
*/
|
462
|
+
closest(selector) {
|
463
|
+
return this.is(selector) ? this : this.parents().filter(selector).first();
|
464
|
+
}
|
465
|
+
|
466
|
+
/**
|
467
|
+
* Shallow renders the current node and returns a shallow wrapper around it.
|
468
|
+
*
|
469
|
+
* NOTE: can only be called on wrapper of a single node.
|
470
|
+
*
|
471
|
+
* @param options object
|
472
|
+
* @returns {ShallowWrapper}
|
473
|
+
*/
|
474
|
+
shallow(options) {
|
475
|
+
return this.single((n) => new ShallowWrapper(n, null, options));
|
476
|
+
}
|
477
|
+
|
478
|
+
/**
|
479
|
+
* Returns the value of prop with the given name of the root node.
|
480
|
+
*
|
481
|
+
* @param propName
|
482
|
+
* @returns {*}
|
483
|
+
*/
|
484
|
+
prop(propName) {
|
485
|
+
return this.props()[propName];
|
486
|
+
}
|
487
|
+
|
488
|
+
/**
|
489
|
+
* Returns the key assigned to the current node.
|
490
|
+
*
|
491
|
+
* @returns {String}
|
492
|
+
*/
|
493
|
+
key() {
|
494
|
+
return this.single((n) => n.key);
|
495
|
+
}
|
496
|
+
|
497
|
+
/**
|
498
|
+
* Returns the type of the root node of this wrapper. If it's a composite component, this will be
|
499
|
+
* the component constructor. If it's native DOM node, it will be a string.
|
500
|
+
*
|
501
|
+
* @returns {String|Function}
|
502
|
+
*/
|
503
|
+
type() {
|
504
|
+
return this.single(typeOfNode);
|
505
|
+
}
|
506
|
+
|
507
|
+
/**
|
508
|
+
* Returns whether or not the current root node has the given class name or not.
|
509
|
+
*
|
510
|
+
* NOTE: can only be called on a wrapper of a single node.
|
511
|
+
*
|
512
|
+
* @param className
|
513
|
+
* @returns {Boolean}
|
514
|
+
*/
|
515
|
+
hasClass(className) {
|
516
|
+
if (className && className.indexOf('.') !== -1) {
|
517
|
+
console.warn(
|
518
|
+
'It looks like you\'re calling `ShallowWrapper::hasClass()` with a CSS selector. ' +
|
519
|
+
'hasClass() expects a class name, not a CSS selector.'
|
520
|
+
);
|
521
|
+
}
|
522
|
+
return this.single(n => hasClassName(n, className));
|
523
|
+
}
|
524
|
+
|
525
|
+
/**
|
526
|
+
* Iterates through each node of the current wrapper and executes the provided function with a
|
527
|
+
* wrapper around the corresponding node passed in as the first argument.
|
528
|
+
*
|
529
|
+
* @param {Function} fn
|
530
|
+
* @returns {ShallowWrapper}
|
531
|
+
*/
|
532
|
+
forEach(fn) {
|
533
|
+
this.nodes.forEach((n, i) => fn.call(this, this.wrap(n), i));
|
534
|
+
return this;
|
535
|
+
}
|
536
|
+
|
537
|
+
/**
|
538
|
+
* Maps the current array of nodes to another array. Each node is passed in as a `ShallowWrapper`
|
539
|
+
* to the map function.
|
540
|
+
*
|
541
|
+
* @param {Function} fn
|
542
|
+
* @returns {Array}
|
543
|
+
*/
|
544
|
+
map(fn) {
|
545
|
+
return this.nodes.map((n, i) => fn.call(this, this.wrap(n), i));
|
546
|
+
}
|
547
|
+
|
548
|
+
/**
|
549
|
+
* Reduces the current array of nodes to a value. Each node is passed in as a `ShallowWrapper`
|
550
|
+
* to the reducer function.
|
551
|
+
*
|
552
|
+
* @param {Function} fn - the reducer function
|
553
|
+
* @param {*} initialValue - the initial value
|
554
|
+
* @returns {*}
|
555
|
+
*/
|
556
|
+
reduce(fn, initialValue) {
|
557
|
+
return this.nodes.reduce(
|
558
|
+
(accum, n, i) => fn.call(this, accum, this.wrap(n), i),
|
559
|
+
initialValue
|
560
|
+
);
|
561
|
+
}
|
562
|
+
|
563
|
+
/**
|
564
|
+
* Reduces the current array of nodes to another array, from right to left. Each node is passed
|
565
|
+
* in as a `ShallowWrapper` to the reducer function.
|
566
|
+
*
|
567
|
+
* @param {Function} fn - the reducer function
|
568
|
+
* @param {*} initialValue - the initial value
|
569
|
+
* @returns {*}
|
570
|
+
*/
|
571
|
+
reduceRight(fn, initialValue) {
|
572
|
+
return this.nodes.reduceRight(
|
573
|
+
(accum, n, i) => fn.call(this, accum, this.wrap(n), i),
|
574
|
+
initialValue
|
575
|
+
);
|
576
|
+
}
|
577
|
+
|
578
|
+
/**
|
579
|
+
* Returns whether or not any of the nodes in the wrapper match the provided selector.
|
580
|
+
*
|
581
|
+
* @param {Function|String} selector
|
582
|
+
* @returns {Boolean}
|
583
|
+
*/
|
584
|
+
some(selector) {
|
585
|
+
const predicate = buildPredicate(selector);
|
586
|
+
return this.nodes.some(predicate);
|
587
|
+
}
|
588
|
+
|
589
|
+
/**
|
590
|
+
* Returns whether or not any of the nodes in the wrapper pass the provided predicate function.
|
591
|
+
*
|
592
|
+
* @param {Function} predicate
|
593
|
+
* @returns {Boolean}
|
594
|
+
*/
|
595
|
+
someWhere(predicate) {
|
596
|
+
return this.nodes.some((n, i) => predicate.call(this, this.wrap(n), i));
|
597
|
+
}
|
598
|
+
|
599
|
+
/**
|
600
|
+
* Returns whether or not all of the nodes in the wrapper match the provided selector.
|
601
|
+
*
|
602
|
+
* @param {Function|String} selector
|
603
|
+
* @returns {Boolean}
|
604
|
+
*/
|
605
|
+
every(selector) {
|
606
|
+
const predicate = buildPredicate(selector);
|
607
|
+
return this.nodes.every(predicate);
|
608
|
+
}
|
609
|
+
|
610
|
+
/**
|
611
|
+
* Returns whether or not any of the nodes in the wrapper pass the provided predicate function.
|
612
|
+
*
|
613
|
+
* @param {Function} predicate
|
614
|
+
* @returns {Boolean}
|
615
|
+
*/
|
616
|
+
everyWhere(predicate) {
|
617
|
+
return this.nodes.every((n, i) => predicate.call(this, this.wrap(n), i));
|
618
|
+
}
|
619
|
+
|
620
|
+
/**
|
621
|
+
* Utility method used to create new wrappers with a mapping function that returns an array of
|
622
|
+
* nodes in response to a single node wrapper. The returned wrapper is a single wrapper around
|
623
|
+
* all of the mapped nodes flattened (and de-duplicated).
|
624
|
+
*
|
625
|
+
* @param {Function} fn
|
626
|
+
* @returns {ShallowWrapper}
|
627
|
+
*/
|
628
|
+
flatMap(fn) {
|
629
|
+
const nodes = this.nodes.map((n, i) => fn.call(this, this.wrap(n), i));
|
630
|
+
const flattened = flatten(nodes, true);
|
631
|
+
const uniques = unique(flattened);
|
632
|
+
return this.wrap(uniques);
|
633
|
+
}
|
634
|
+
|
635
|
+
/**
|
636
|
+
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
|
637
|
+
* function. The predicate function will receive the nodes inside a ShallowWrapper as its
|
638
|
+
* first argument.
|
639
|
+
*
|
640
|
+
* @param {Function} predicate
|
641
|
+
* @returns {ShallowWrapper}
|
642
|
+
*/
|
643
|
+
findWhere(predicate) {
|
644
|
+
return findWhereUnwrapped(this, n => predicate(this.wrap(n)));
|
645
|
+
}
|
646
|
+
|
647
|
+
/**
|
648
|
+
* Returns the node at a given index of the current wrapper.
|
649
|
+
*
|
650
|
+
* @param index
|
651
|
+
* @returns {ReactElement}
|
652
|
+
*/
|
653
|
+
get(index) {
|
654
|
+
return this.nodes[index];
|
655
|
+
}
|
656
|
+
|
657
|
+
/**
|
658
|
+
* Returns a wrapper around the node at a given index of the current wrapper.
|
659
|
+
*
|
660
|
+
* @param index
|
661
|
+
* @returns {ShallowWrapper}
|
662
|
+
*/
|
663
|
+
at(index) {
|
664
|
+
return this.wrap(this.nodes[index]);
|
665
|
+
}
|
666
|
+
|
667
|
+
/**
|
668
|
+
* Returns a wrapper around the first node of the current wrapper.
|
669
|
+
*
|
670
|
+
* @returns {ShallowWrapper}
|
671
|
+
*/
|
672
|
+
first() {
|
673
|
+
return this.at(0);
|
674
|
+
}
|
675
|
+
|
676
|
+
/**
|
677
|
+
* Returns a wrapper around the last node of the current wrapper.
|
678
|
+
*
|
679
|
+
* @returns {ShallowWrapper}
|
680
|
+
*/
|
681
|
+
last() {
|
682
|
+
return this.at(this.length - 1);
|
683
|
+
}
|
684
|
+
|
685
|
+
/**
|
686
|
+
* Returns true if the current wrapper has no nodes. False otherwise.
|
687
|
+
*
|
688
|
+
* @returns {boolean}
|
689
|
+
*/
|
690
|
+
isEmpty() {
|
691
|
+
return this.length === 0;
|
692
|
+
}
|
693
|
+
|
694
|
+
/**
|
695
|
+
* Utility method that throws an error if the current instance has a length other than one.
|
696
|
+
* This is primarily used to enforce that certain methods are only run on a wrapper when it is
|
697
|
+
* wrapping a single node.
|
698
|
+
*
|
699
|
+
* @param fn
|
700
|
+
* @returns {*}
|
701
|
+
*/
|
702
|
+
single(fn) {
|
703
|
+
if (this.length !== 1) {
|
704
|
+
throw new Error(
|
705
|
+
`This method is only meant to be run on single node. ${this.length} found instead.`
|
706
|
+
);
|
707
|
+
}
|
708
|
+
return fn.call(this, this.node);
|
709
|
+
}
|
710
|
+
|
711
|
+
/**
|
712
|
+
* Helpful utility method to create a new wrapper with the same root as the current wrapper, with
|
713
|
+
* any nodes passed in as the first parameter automatically wrapped.
|
714
|
+
*
|
715
|
+
* @param node
|
716
|
+
* @returns {ShallowWrapper}
|
717
|
+
*/
|
718
|
+
wrap(node) {
|
719
|
+
if (node instanceof ShallowWrapper) {
|
720
|
+
return node;
|
721
|
+
}
|
722
|
+
return new ShallowWrapper(node, this.root);
|
723
|
+
}
|
724
|
+
|
725
|
+
/**
|
726
|
+
* Returns an HTML-like string of the shallow render for debugging purposes.
|
727
|
+
*
|
728
|
+
* @returns {String}
|
729
|
+
*/
|
730
|
+
debug() {
|
731
|
+
return debugNodes(this.nodes);
|
732
|
+
}
|
733
|
+
|
734
|
+
/**
|
735
|
+
* Invokes intercepter and returns itself. intercepter is called with itself.
|
736
|
+
* This is helpful when debugging nodes in method chains.
|
737
|
+
* @param fn
|
738
|
+
* @returns {ShallowWrapper}
|
739
|
+
*/
|
740
|
+
tap(intercepter) {
|
741
|
+
intercepter(this);
|
742
|
+
return this;
|
743
|
+
}
|
744
|
+
}
|