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.
@@ -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
+ }