Improve scry() error message for bad first argument (#13351)
Dan Abramov committed
Aug 9, 2018 at 15:05 UTC
0072b59984f0743b95664557c17904e8c0912ec5
2 files changed
+92
-4
packages/react-dom/src/__tests__/ReactTestUtils-test.js
+56
@@ -281,6 +281,62 @@ describe('ReactTestUtils', () => {
281
expect(hrs.length).toBe(2);
282
});
283
284
+ it('provides a clear error when passing invalid objects to scry', () => {
285
+ // This is probably too relaxed but it's existing behavior.
286
+ ReactTestUtils.findAllInRenderedTree(null, 'span');
287
+ ReactTestUtils.findAllInRenderedTree(undefined, 'span');
288
+ ReactTestUtils.findAllInRenderedTree('', 'span');
289
+ ReactTestUtils.findAllInRenderedTree(0, 'span');
290
+ ReactTestUtils.findAllInRenderedTree(false, 'span');
291
+
292
+ expect(() => {
293
+ ReactTestUtils.findAllInRenderedTree([], 'span');
294
+ }).toThrow(
295
+ 'findAllInRenderedTree(...): the first argument must be a React class instance. ' +
296
+ 'Instead received: an array.',
297
+ );
298
+ expect(() => {
299
+ ReactTestUtils.scryRenderedDOMComponentsWithClass(10, 'button');
300
+ }).toThrow(
301
+ 'scryRenderedDOMComponentsWithClass(...): the first argument must be a React class instance. ' +
302
+ 'Instead received: 10.',
303
+ );
304
+ expect(() => {
305
+ ReactTestUtils.findRenderedDOMComponentWithClass('hello', 'button');
306
+ }).toThrow(
307
+ 'findRenderedDOMComponentWithClass(...): the first argument must be a React class instance. ' +
308
+ 'Instead received: hello.',
309
+ );
310
+ expect(() => {
311
+ ReactTestUtils.scryRenderedDOMComponentsWithTag(
312
+ {x: true, y: false},
313
+ 'span',
314
+ );
315
+ }).toThrow(
316
+ 'scryRenderedDOMComponentsWithTag(...): the first argument must be a React class instance. ' +
317
+ 'Instead received: object with keys {x, y}.',
318
+ );
319
+ const div = document.createElement('div');
320
+ expect(() => {
321
+ ReactTestUtils.findRenderedDOMComponentWithTag(div, 'span');
322
+ }).toThrow(
323
+ 'findRenderedDOMComponentWithTag(...): the first argument must be a React class instance. ' +
324
+ 'Instead received: a DOM node.',
325
+ );
326
+ expect(() => {
327
+ ReactTestUtils.scryRenderedComponentsWithType(true, 'span');
328
+ }).toThrow(
329
+ 'scryRenderedComponentsWithType(...): the first argument must be a React class instance. ' +
330
+ 'Instead received: true.',
331
+ );
332
+ expect(() => {
333
+ ReactTestUtils.findRenderedComponentWithType(true, 'span');
334
+ }).toThrow(
335
+ 'findRenderedComponentWithType(...): the first argument must be a React class instance. ' +
336
+ 'Instead received: true.',
337
+ );
338
+ });
339
+
340
describe('Simulate', () => {
341
it('should change the value of an input field', () => {
342
const obj = {
packages/react-dom/src/test-utils/ReactTestUtils.js
+36
-4
@@ -107,6 +107,35 @@ function findAllInRenderedFiberTreeInternal(fiber, test) {
107
}
108
}
109
110
+function validateClassInstance(inst, methodName) {
111
+ if (!inst) {
112
+ // This is probably too relaxed but it's existing behavior.
113
+ return;
114
+ }
115
+ if (ReactInstanceMap.get(inst)) {
116
+ // This is a public instance indeed.
117
+ return;
118
+ }
119
+ let received;
120
+ const stringified = '' + inst;
121
+ if (Array.isArray(inst)) {
122
+ received = 'an array';
123
+ } else if (inst && inst.nodeType === 1 && inst.tagName) {
124
+ received = 'a DOM node';
125
+ } else if (stringified === '[object Object]') {
126
+ received = 'object with keys {' + Object.keys(inst).join(', ') + '}';
127
+ } else {
128
+ received = stringified;
129
+ }
130
+ invariant(
131
+ false,
132
+ '%s(...): the first argument must be a React class instance. ' +
133
+ 'Instead received: %s.',
134
+ methodName,
135
+ received,
136
+ );
137
+}
138
+
139
/**
140
* Utilities for making it easy to test React components.
141
*
@@ -166,13 +195,10 @@ const ReactTestUtils = {
195
},
196
197
findAllInRenderedTree: function(inst, test) {
198
+ validateClassInstance(inst, 'findAllInRenderedTree');
199
if (!inst) {
200
return [];
201
}
172
- invariant(
173
- ReactTestUtils.isCompositeComponent(inst),
174
- 'findAllInRenderedTree(...): instance must be a composite component',
175
- );
202
const internalInstance = ReactInstanceMap.get(inst);
203
return findAllInRenderedFiberTreeInternal(internalInstance, test);
204
},
@@ -183,6 +209,7 @@ const ReactTestUtils = {
209
* @return {array} an array of all the matches.
210
*/
211
scryRenderedDOMComponentsWithClass: function(root, classNames) {
212
+ validateClassInstance(root, 'scryRenderedDOMComponentsWithClass');
213
return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
214
if (ReactTestUtils.isDOMComponent(inst)) {
215
let className = inst.className;
@@ -215,6 +242,7 @@ const ReactTestUtils = {
242
* @return {!ReactDOMComponent} The one match.
243
*/
244
findRenderedDOMComponentWithClass: function(root, className) {
245
+ validateClassInstance(root, 'findRenderedDOMComponentWithClass');
246
const all = ReactTestUtils.scryRenderedDOMComponentsWithClass(
247
root,
248
className,
@@ -237,6 +265,7 @@ const ReactTestUtils = {
265
* @return {array} an array of all the matches.
266
*/
267
scryRenderedDOMComponentsWithTag: function(root, tagName) {
268
+ validateClassInstance(root, 'scryRenderedDOMComponentsWithTag');
269
return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
270
return (
271
ReactTestUtils.isDOMComponent(inst) &&
@@ -252,6 +281,7 @@ const ReactTestUtils = {
281
* @return {!ReactDOMComponent} The one match.
282
*/
283
findRenderedDOMComponentWithTag: function(root, tagName) {
284
+ validateClassInstance(root, 'findRenderedDOMComponentWithTag');
285
const all = ReactTestUtils.scryRenderedDOMComponentsWithTag(root, tagName);
286
if (all.length !== 1) {
287
throw new Error(
@@ -270,6 +300,7 @@ const ReactTestUtils = {
300
* @return {array} an array of all the matches.
301
*/
302
scryRenderedComponentsWithType: function(root, componentType) {
303
+ validateClassInstance(root, 'scryRenderedComponentsWithType');
304
return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
305
return ReactTestUtils.isCompositeComponentWithType(inst, componentType);
306
});
@@ -282,6 +313,7 @@ const ReactTestUtils = {
313
* @return {!ReactComponent} The one match.
314
*/
315
findRenderedComponentWithType: function(root, componentType) {
316
+ validateClassInstance(root, 'findRenderedComponentWithType');
317
const all = ReactTestUtils.scryRenderedComponentsWithType(
318
root,
319
componentType,