@samitouri / QOS-React / commits / b5334a44e9

toWarnInDev matcher; throw on unexpected console.error (#11786)

* Added toWarnInDev matcher and connected to 1 test * Added .toLowPriorityWarnDev() matcher * Reply Jest spy with custom spy. Unregister spy after toWarnDev() so unexpected console.error/warn calls will fail tests. * console warn/error throws immediately in tests by default (if not spied on) * Pass-thru console message before erroring to make it easier to identify * More robustly handle unexpected warnings within try/catch * Error message includes remaining expected warnings in addition to unexpected warning

Brian Vaughn committed Jan 2, 2018 at 11:06 UTC b5334a44e99c32ca26f7d65ca8312e13f7b7f4b7
20 files changed +978 -1381
packages/react-art/src/__tests__/ReactART-test.js
+28 -49
@@ -336,10 +336,6 @@ describe('ReactART', () => {
336 });
337
338 describe('ReactARTComponents', () => {
339 - function normalizeCodeLocInfo(str) {
340 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
341 - }
342 -
339 it('should generate a <Shape> with props for drawing the Circle', () => {
340 const circle = renderer.create(
341 <Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
@@ -348,16 +344,13 @@ describe('ReactARTComponents', () => {
344 });
345
346 it('should warn if radius is missing on a Circle component', () => {
351 - spyOnDev(console, 'error');
352 - renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />);
353 - if (__DEV__) {
354 - expect(console.error.calls.count()).toBe(1);
355 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
356 - 'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
357 - 'but its value is `undefined`.' +
358 - '\n in Circle (at **)',
359 - );
360 - }
347 + expect(() =>
348 + renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />),
349 + ).toWarnDev(
350 + 'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
351 + 'but its value is `undefined`.' +
352 + '\n in Circle (at **)',
353 + );
354 });
355
356 it('should generate a <Shape> with props for drawing the Rectangle', () => {
@@ -368,21 +361,16 @@ describe('ReactARTComponents', () => {
361 });
362
363 it('should warn if width/height is missing on a Rectangle component', () => {
371 - spyOnDev(console, 'error');
372 - renderer.create(<Rectangle stroke="green" fill="blue" />);
373 - if (__DEV__) {
374 - expect(console.error.calls.count()).toBe(2);
375 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
376 - 'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
377 - 'but its value is `undefined`.' +
378 - '\n in Rectangle (at **)',
379 - );
380 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
381 - 'Warning: Failed prop type: The prop `height` is marked as required in `Rectangle`, ' +
382 - 'but its value is `undefined`.' +
383 - '\n in Rectangle (at **)',
384 - );
385 - }
364 + expect(() =>
365 + renderer.create(<Rectangle stroke="green" fill="blue" />),
366 + ).toWarnDev([
367 + 'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
368 + 'but its value is `undefined`.' +
369 + '\n in Rectangle (at **)',
370 + 'Warning: Failed prop type: The prop `height` is marked as required in `Rectangle`, ' +
371 + 'but its value is `undefined`.' +
372 + '\n in Rectangle (at **)',
373 + ]);
374 });
375
376 it('should generate a <Shape> with props for drawing the Wedge', () => {
@@ -400,25 +388,16 @@ describe('ReactARTComponents', () => {
388 });
389
390 it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
403 - spyOnDev(console, 'error');
404 - renderer.create(<Wedge fill="blue" />);
405 - if (__DEV__) {
406 - expect(console.error.calls.count()).toBe(3);
407 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
408 - 'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
409 - 'but its value is `undefined`.' +
410 - '\n in Wedge (at **)',
411 - );
412 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
413 - 'Warning: Failed prop type: The prop `startAngle` is marked as required in `Wedge`, ' +
414 - 'but its value is `undefined`.' +
415 - '\n in Wedge (at **)',
416 - );
417 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toEqual(
418 - 'Warning: Failed prop type: The prop `endAngle` is marked as required in `Wedge`, ' +
419 - 'but its value is `undefined`.' +
420 - '\n in Wedge (at **)',
421 - );
422 - }
391 + expect(() => renderer.create(<Wedge fill="blue" />)).toWarnDev([
392 + 'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
393 + 'but its value is `undefined`.' +
394 + '\n in Wedge (at **)',
395 + 'Warning: Failed prop type: The prop `startAngle` is marked as required in `Wedge`, ' +
396 + 'but its value is `undefined`.' +
397 + '\n in Wedge (at **)',
398 + 'Warning: Failed prop type: The prop `endAngle` is marked as required in `Wedge`, ' +
399 + 'but its value is `undefined`.' +
400 + '\n in Wedge (at **)',
401 + ]);
402 });
403 });
packages/react-dom/src/__tests__/CSSPropertyOperations-test.js
+47 -84
@@ -13,10 +13,6 @@ const React = require('react');
13 const ReactDOM = require('react-dom');
14 const ReactDOMServer = require('react-dom/server');
15
16 -function normalizeCodeLocInfo(str) {
17 - return str && str.replace(/at .+?:\d+/g, 'at **');
18 -}
19 -
16 describe('CSSPropertyOperations', () => {
17 it('should automatically append `px` to relevant styles', () => {
18 const styles = {
@@ -91,17 +87,13 @@ describe('CSSPropertyOperations', () => {
87 }
88 }
89
94 - spyOnDev(console, 'error');
90 const root = document.createElement('div');
96 - ReactDOM.render(<Comp />, root);
97 - if (__DEV__) {
98 - expect(console.error.calls.count()).toBe(1);
99 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
100 - 'Warning: Unsupported style property background-color. Did you mean backgroundColor?' +
101 - '\n in div (at **)' +
102 - '\n in Comp (at **)',
103 - );
104 - }
91 +
92 + expect(() => ReactDOM.render(<Comp />, root)).toWarnDev(
93 + 'Warning: Unsupported style property background-color. Did you mean backgroundColor?' +
94 + '\n in div (at **)' +
95 + '\n in Comp (at **)',
96 + );
97 });
98
99 it('should warn when updating hyphenated style names', () => {
@@ -113,28 +105,21 @@ describe('CSSPropertyOperations', () => {
105 }
106 }
107
116 - spyOnDev(console, 'error');
108 const styles = {
109 '-ms-transform': 'translate3d(0, 0, 0)',
110 '-webkit-transform': 'translate3d(0, 0, 0)',
111 };
112 const root = document.createElement('div');
113 ReactDOM.render(<Comp />, root);
123 - ReactDOM.render(<Comp style={styles} />, root);
124 -
125 - if (__DEV__) {
126 - expect(console.error.calls.count()).toBe(2);
127 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
128 - 'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' +
129 - '\n in div (at **)' +
130 - '\n in Comp (at **)',
131 - );
132 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
133 - 'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' +
134 - '\n in div (at **)' +
135 - '\n in Comp (at **)',
136 - );
137 - }
114 +
115 + expect(() => ReactDOM.render(<Comp style={styles} />, root)).toWarnDev([
116 + 'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' +
117 + '\n in div (at **)' +
118 + '\n in Comp (at **)',
119 + 'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' +
120 + '\n in div (at **)' +
121 + '\n in Comp (at **)',
122 + ]);
123 });
124
125 it('warns when miscapitalizing vendored style names', () => {
@@ -154,25 +139,19 @@ describe('CSSPropertyOperations', () => {
139 }
140 }
141
157 - spyOnDev(console, 'error');
142 const root = document.createElement('div');
159 - ReactDOM.render(<Comp />, root);
160 - if (__DEV__) {
143 +
144 + expect(() => ReactDOM.render(<Comp />, root)).toWarnDev([
145 // msTransform is correct already and shouldn't warn
162 - expect(console.error.calls.count()).toBe(2);
163 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
164 - 'Warning: Unsupported vendor-prefixed style property oTransform. ' +
165 - 'Did you mean OTransform?' +
166 - '\n in div (at **)' +
167 - '\n in Comp (at **)',
168 - );
169 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
170 - 'Warning: Unsupported vendor-prefixed style property webkitTransform. ' +
171 - 'Did you mean WebkitTransform?' +
172 - '\n in div (at **)' +
173 - '\n in Comp (at **)',
174 - );
175 - }
146 + 'Warning: Unsupported vendor-prefixed style property oTransform. ' +
147 + 'Did you mean OTransform?' +
148 + '\n in div (at **)' +
149 + '\n in Comp (at **)',
150 + 'Warning: Unsupported vendor-prefixed style property webkitTransform. ' +
151 + 'Did you mean WebkitTransform?' +
152 + '\n in div (at **)' +
153 + '\n in Comp (at **)',
154 + ]);
155 });
156
157 it('should warn about style having a trailing semicolon', () => {
@@ -193,24 +172,18 @@ describe('CSSPropertyOperations', () => {
172 }
173 }
174
196 - spyOnDev(console, 'error');
175 const root = document.createElement('div');
198 - ReactDOM.render(<Comp />, root);
199 - if (__DEV__) {
200 - expect(console.error.calls.count()).toBe(2);
201 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
202 - "Warning: Style property values shouldn't contain a semicolon. " +
203 - 'Try "backgroundColor: blue" instead.' +
204 - '\n in div (at **)' +
205 - '\n in Comp (at **)',
206 - );
207 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
208 - "Warning: Style property values shouldn't contain a semicolon. " +
209 - 'Try "color: red" instead.' +
210 - '\n in div (at **)' +
211 - '\n in Comp (at **)',
212 - );
213 - }
176 +
177 + expect(() => ReactDOM.render(<Comp />, root)).toWarnDev([
178 + "Warning: Style property values shouldn't contain a semicolon. " +
179 + 'Try "backgroundColor: blue" instead.' +
180 + '\n in div (at **)' +
181 + '\n in Comp (at **)',
182 + "Warning: Style property values shouldn't contain a semicolon. " +
183 + 'Try "color: red" instead.' +
184 + '\n in div (at **)' +
185 + '\n in Comp (at **)',
186 + ]);
187 });
188
189 it('should warn about style containing a NaN value', () => {
@@ -222,18 +195,13 @@ describe('CSSPropertyOperations', () => {
195 }
196 }
197
225 - spyOnDev(console, 'error');
198 const root = document.createElement('div');
227 - ReactDOM.render(<Comp />, root);
199
229 - if (__DEV__) {
230 - expect(console.error.calls.count()).toBe(1);
231 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
232 - 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
233 - '\n in div (at **)' +
234 - '\n in Comp (at **)',
235 - );
236 - }
200 + expect(() => ReactDOM.render(<Comp />, root)).toWarnDev(
201 + 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
202 + '\n in div (at **)' +
203 + '\n in Comp (at **)',
204 + );
205 });
206
207 it('should not warn when setting CSS custom properties', () => {
@@ -256,18 +224,13 @@ describe('CSSPropertyOperations', () => {
224 }
225 }
226
259 - spyOnDev(console, 'error');
227 const root = document.createElement('div');
261 - ReactDOM.render(<Comp />, root);
228
263 - if (__DEV__) {
264 - expect(console.error.calls.count()).toBe(1);
265 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
266 - 'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' +
267 - '\n in div (at **)' +
268 - '\n in Comp (at **)',
269 - );
270 - }
229 + expect(() => ReactDOM.render(<Comp />, root)).toWarnDev(
230 + 'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' +
231 + '\n in div (at **)' +
232 + '\n in Comp (at **)',
233 + );
234 });
235
236 it('should not add units to CSS custom properties', () => {
packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
+7 -10
@@ -151,25 +151,22 @@ describe('DOMPropertyOperations', () => {
151
152 it('should not remove attributes for special properties', () => {
153 const container = document.createElement('div');
154 - spyOnDev(console, 'error');
154 ReactDOM.render(
155 <input type="text" value="foo" onChange={function() {}} />,
156 container,
157 );
158 expect(container.firstChild.getAttribute('value')).toBe('foo');
159 expect(container.firstChild.value).toBe('foo');
161 - ReactDOM.render(
162 - <input type="text" onChange={function() {}} />,
163 - container,
160 + expect(() =>
161 + ReactDOM.render(
162 + <input type="text" onChange={function() {}} />,
163 + container,
164 + ),
165 + ).toWarnDev(
166 + 'A component is changing a controlled input of type text to be uncontrolled',
167 );
168 expect(container.firstChild.getAttribute('value')).toBe('foo');
169 expect(container.firstChild.value).toBe('foo');
167 - if (__DEV__) {
168 - expect(console.error.calls.count()).toBe(1);
169 - expect(console.error.calls.argsFor(0)[0]).toContain(
170 - 'A component is changing a controlled input of type text to be uncontrolled',
171 - );
172 - }
170 });
171 });
172 });
packages/react-dom/src/__tests__/EventPluginHub-test.js
+8 -12
@@ -22,21 +22,17 @@ describe('EventPluginHub', () => {
22 });
23
24 it('should prevent non-function listeners, at dispatch', () => {
25 - spyOnDev(console, 'error');
26 - const node = ReactTestUtils.renderIntoDocument(
27 - <div onClick="not a function" />,
25 + let node;
26 + expect(() => {
27 + node = ReactTestUtils.renderIntoDocument(
28 + <div onClick="not a function" />,
29 + );
30 + }).toWarnDev(
31 + 'Expected `onClick` listener to be a function, instead got a value of `string` type.',
32 );
29 - expect(function() {
30 - ReactTestUtils.SimulateNative.click(node);
31 - }).toThrowError(
33 + expect(() => ReactTestUtils.SimulateNative.click(node)).toThrowError(
34 'Expected `onClick` listener to be a function, instead got a value of `string` type.',
35 );
34 - if (__DEV__) {
35 - expect(console.error.calls.count()).toBe(1);
36 - expect(console.error.calls.argsFor(0)[0]).toContain(
37 - 'Expected `onClick` listener to be a function, instead got a value of `string` type.',
38 - );
39 - }
36 });
37
38 it('should not prevent null listeners, at dispatch', () => {
packages/react-dom/src/__tests__/ReactChildReconciler-test.js
+34 -66
@@ -16,10 +16,6 @@ let React;
16 let ReactTestUtils;
17
18 describe('ReactChildReconciler', () => {
19 - function normalizeCodeLocInfo(str) {
20 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
21 - }
22 -
19 beforeEach(() => {
20 jest.resetModules();
21
@@ -46,30 +42,21 @@ describe('ReactChildReconciler', () => {
42 }
43
44 it('warns for duplicated array keys', () => {
49 - spyOnDev(console, 'error');
50 -
45 class Component extends React.Component {
46 render() {
47 return <div>{[<div key="1" />, <div key="1" />]}</div>;
48 }
49 }
50
57 - ReactTestUtils.renderIntoDocument(<Component />);
58 -
59 - if (__DEV__) {
60 - expect(console.error.calls.count()).toBe(1);
61 - expect(console.error.calls.argsFor(0)[0]).toContain(
62 - 'Keys should be unique so that components maintain their identity ' +
63 - 'across updates. Non-unique keys may cause children to be ' +
64 - 'duplicated and/or omitted — the behavior is unsupported and ' +
65 - 'could change in a future version.',
66 - );
67 - }
51 + expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
52 + 'Keys should be unique so that components maintain their identity ' +
53 + 'across updates. Non-unique keys may cause children to be ' +
54 + 'duplicated and/or omitted — the behavior is unsupported and ' +
55 + 'could change in a future version.',
56 + );
57 });
58
59 it('warns for duplicated array keys with component stack info', () => {
71 - spyOnDev(console, 'error');
72 -
60 class Component extends React.Component {
61 render() {
62 return <div>{[<div key="1" />, <div key="1" />]}</div>;
@@ -88,49 +75,35 @@ describe('ReactChildReconciler', () => {
75 }
76 }
77
91 - ReactTestUtils.renderIntoDocument(<GrandParent />);
92 -
93 - if (__DEV__) {
94 - expect(console.error.calls.count()).toBe(1);
95 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
96 - 'Encountered two children with the same key, `1`. ' +
97 - 'Keys should be unique so that components maintain their identity ' +
98 - 'across updates. Non-unique keys may cause children to be ' +
99 - 'duplicated and/or omitted — the behavior is unsupported and ' +
100 - 'could change in a future version.',
101 - ' in div (at **)\n' +
102 - ' in Component (at **)\n' +
103 - ' in Parent (at **)\n' +
104 - ' in GrandParent (at **)',
105 - );
106 - }
78 + expect(() => ReactTestUtils.renderIntoDocument(<GrandParent />)).toWarnDev(
79 + 'Encountered two children with the same key, `1`. ' +
80 + 'Keys should be unique so that components maintain their identity ' +
81 + 'across updates. Non-unique keys may cause children to be ' +
82 + 'duplicated and/or omitted — the behavior is unsupported and ' +
83 + 'could change in a future version.',
84 + ' in div (at **)\n' +
85 + ' in Component (at **)\n' +
86 + ' in Parent (at **)\n' +
87 + ' in GrandParent (at **)',
88 + );
89 });
90
91 it('warns for duplicated iterable keys', () => {
110 - spyOnDev(console, 'error');
111 -
92 class Component extends React.Component {
93 render() {
94 return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
95 }
96 }
97
118 - ReactTestUtils.renderIntoDocument(<Component />);
119 -
120 - if (__DEV__) {
121 - expect(console.error.calls.count()).toBe(1);
122 - expect(console.error.calls.argsFor(0)[0]).toContain(
123 - 'Keys should be unique so that components maintain their identity ' +
124 - 'across updates. Non-unique keys may cause children to be ' +
125 - 'duplicated and/or omitted — the behavior is unsupported and ' +
126 - 'could change in a future version.',
127 - );
128 - }
98 + expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
99 + 'Keys should be unique so that components maintain their identity ' +
100 + 'across updates. Non-unique keys may cause children to be ' +
101 + 'duplicated and/or omitted — the behavior is unsupported and ' +
102 + 'could change in a future version.',
103 + );
104 });
105
106 it('warns for duplicated iterable keys with component stack info', () => {
132 - spyOnDev(console, 'error');
133 -
107 class Component extends React.Component {
108 render() {
109 return <div>{createIterable([<div key="1" />, <div key="1" />])}</div>;
@@ -149,21 +122,16 @@ describe('ReactChildReconciler', () => {
122 }
123 }
124
152 - ReactTestUtils.renderIntoDocument(<GrandParent />);
153 -
154 - if (__DEV__) {
155 - expect(console.error.calls.count()).toBe(1);
156 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
157 - 'Encountered two children with the same key, `1`. ' +
158 - 'Keys should be unique so that components maintain their identity ' +
159 - 'across updates. Non-unique keys may cause children to be ' +
160 - 'duplicated and/or omitted — the behavior is unsupported and ' +
161 - 'could change in a future version.',
162 - ' in div (at **)\n' +
163 - ' in Component (at **)\n' +
164 - ' in Parent (at **)\n' +
165 - ' in GrandParent (at **)',
166 - );
167 - }
125 + expect(() => ReactTestUtils.renderIntoDocument(<GrandParent />)).toWarnDev(
126 + 'Encountered two children with the same key, `1`. ' +
127 + 'Keys should be unique so that components maintain their identity ' +
128 + 'across updates. Non-unique keys may cause children to be ' +
129 + 'duplicated and/or omitted — the behavior is unsupported and ' +
130 + 'could change in a future version.',
131 + ' in div (at **)\n' +
132 + ' in Component (at **)\n' +
133 + ' in Parent (at **)\n' +
134 + ' in GrandParent (at **)',
135 + );
136 });
137 });
packages/react/src/__tests__/ReactChildren-test.js
+19 -45
@@ -13,10 +13,6 @@ describe('ReactChildren', () => {
13 let React;
14 let ReactTestUtils;
15
16 - function normalizeCodeLocInfo(str) {
17 - return str && str.replace(/at .+?:\d+/g, 'at **');
18 - }
19 -
16 beforeEach(() => {
17 jest.resetModules();
18 React = require('react');
@@ -352,7 +348,6 @@ describe('ReactChildren', () => {
348 });
349
350 it('should be called for each child in an iterable without keys', () => {
355 - spyOnDev(console, 'error');
351 const threeDivIterable = {
352 '@@iterator': function() {
353 let i = 0;
@@ -374,7 +369,10 @@ describe('ReactChildren', () => {
369 return kid;
370 });
371
377 - const instance = <div>{threeDivIterable}</div>;
372 + let instance;
373 + expect(() => (instance = <div>{threeDivIterable}</div>)).toWarnDev(
374 + 'Warning: Each child in an array or iterator should have a unique "key" prop.',
375 + );
376
377 function assertCalls() {
378 expect(callback.calls.count()).toBe(3);
@@ -386,13 +384,6 @@ describe('ReactChildren', () => {
384
385 React.Children.forEach(instance.props.children, callback, context);
386 assertCalls();
389 - if (__DEV__) {
390 - expect(console.error.calls.count()).toBe(1);
391 - expect(console.error.calls.argsFor(0)[0]).toContain(
392 - 'Warning: Each child in an array or iterator should have a unique "key" prop.',
393 - );
394 - console.error.calls.reset();
395 - }
387
388 const mappedChildren = React.Children.map(
389 instance.props.children,
@@ -400,9 +391,6 @@ describe('ReactChildren', () => {
391 context,
392 );
393 assertCalls();
403 - if (__DEV__) {
404 - expect(console.error.calls.count()).toBe(0);
405 - }
394 expect(mappedChildren).toEqual([
395 <div key=".0" />,
396 <div key=".1" />,
@@ -958,28 +946,23 @@ describe('ReactChildren', () => {
946
947 describe('with fragments enabled', () => {
948 it('warns for keys for arrays of elements in a fragment', () => {
961 - spyOnDev(console, 'error');
949 class ComponentReturningArray extends React.Component {
950 render() {
951 return [<div />, <div />];
952 }
953 }
954
968 - ReactTestUtils.renderIntoDocument(<ComponentReturningArray />);
969 -
970 - if (__DEV__) {
971 - expect(console.error.calls.count()).toBe(1);
972 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
973 - 'Warning: ' +
974 - 'Each child in an array or iterator should have a unique "key" prop.' +
975 - ' See https://fb.me/react-warning-keys for more information.' +
976 - '\n in ComponentReturningArray (at **)',
977 - );
978 - }
955 + expect(() =>
956 + ReactTestUtils.renderIntoDocument(<ComponentReturningArray />),
957 + ).toWarnDev(
958 + 'Warning: ' +
959 + 'Each child in an array or iterator should have a unique "key" prop.' +
960 + ' See https://fb.me/react-warning-keys for more information.' +
961 + '\n in ComponentReturningArray (at **)',
962 + );
963 });
964
965 it('does not warn when there are keys on elements in a fragment', () => {
982 - spyOnDev(console, 'error');
966 class ComponentReturningArray extends React.Component {
967 render() {
968 return [<div key="foo" />, <div key="bar" />];
@@ -987,25 +970,16 @@ describe('ReactChildren', () => {
970 }
971
972 ReactTestUtils.renderIntoDocument(<ComponentReturningArray />);
990 -
991 - if (__DEV__) {
992 - expect(console.error.calls.count()).toBe(0);
993 - }
973 });
974
975 it('warns for keys for arrays at the top level', () => {
997 - spyOnDev(console, 'error');
998 -
999 - ReactTestUtils.renderIntoDocument([<div />, <div />]);
1000 -
1001 - if (__DEV__) {
1002 - expect(console.error.calls.count()).toBe(1);
1003 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
1004 - 'Warning: ' +
1005 - 'Each child in an array or iterator should have a unique "key" prop.' +
1006 - ' See https://fb.me/react-warning-keys for more information.',
1007 - );
1008 - }
976 + expect(() =>
977 + ReactTestUtils.renderIntoDocument([<div />, <div />]),
978 + ).toWarnDev(
979 + 'Warning: ' +
980 + 'Each child in an array or iterator should have a unique "key" prop.' +
981 + ' See https://fb.me/react-warning-keys for more information.',
982 + );
983 });
984 });
985 });
packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+38 -63
@@ -46,14 +46,12 @@ describe 'ReactCoffeeScriptClass', ->
46 expect(Foo.name).toBe 'Foo'
47
48 it 'throws if no render function is defined', ->
49 - spyOnDev console, 'error'
49 class Foo extends React.Component
50 expect(->
52 - ReactDOM.render React.createElement(Foo), container
53 - ).toThrow()
54 - if __DEV__
55 - expect(console.error.calls.count()).toBe(1)
56 - expect(console.error.calls.argsFor(0)[0]).toContain('No `render` method found on the returned component instance')
51 + expect(->
52 + ReactDOM.render React.createElement(Foo), container
53 + ).toThrow()
54 + ).toWarnDev('No `render` method found on the returned component instance')
55 undefined
56
57 it 'renders a simple stateless component with prop', ->
@@ -150,8 +148,6 @@ describe 'ReactCoffeeScriptClass', ->
148 undefined
149
150 it 'should warn with non-object in the initial state property', ->
153 - spyOnDev console, 'error'
154 -
151 [['an array'], 'a string', 1234].forEach (state) ->
152 class Foo extends React.Component
153 constructor: ->
@@ -160,14 +156,9 @@ describe 'ReactCoffeeScriptClass', ->
156 render: ->
157 span()
158
163 - test React.createElement(Foo), 'SPAN', ''
164 - if __DEV__
165 - expect(console.error.calls.count()).toBe 1
166 - expect(console.error.calls.argsFor(0)[0]).toContain(
167 - 'Foo.state: must be set to an object or null'
168 - )
169 - console.error.calls.reset()
170 -
159 + expect(->
160 + test React.createElement(Foo), 'SPAN', ''
161 + ).toWarnDev('Foo.state: must be set to an object or null')
162 undefined
163
164 it 'should render with null in the initial state property', ->
@@ -287,7 +278,6 @@ describe 'ReactCoffeeScriptClass', ->
278
279 it 'warns when classic properties are defined on the instance,
280 but does not invoke them.', ->
290 - spyOnDev console, 'error'
281 getInitialStateWasCalled = false
282 getDefaultPropsWasCalled = false
283 class Foo extends React.Component
@@ -307,28 +297,20 @@ describe 'ReactCoffeeScriptClass', ->
297 span
298 className: 'foo'
299
310 - test React.createElement(Foo), 'SPAN', 'foo'
300 + expect(->
301 + test React.createElement(Foo), 'SPAN', 'foo'
302 + ).toWarnDev([
303 + 'getInitialState was defined on Foo, a plain JavaScript class.',
304 + 'getDefaultProps was defined on Foo, a plain JavaScript class.',
305 + 'propTypes was defined as an instance property on Foo.',
306 + 'contextTypes was defined as an instance property on Foo.',
307 + ])
308 expect(getInitialStateWasCalled).toBe false
309 expect(getDefaultPropsWasCalled).toBe false
313 - if __DEV__
314 - expect(console.error.calls.count()).toBe 4
315 - expect(console.error.calls.argsFor(0)[0]).toContain(
316 - 'getInitialState was defined on Foo, a plain JavaScript class.'
317 - )
318 - expect(console.error.calls.argsFor(1)[0]).toContain(
319 - 'getDefaultProps was defined on Foo, a plain JavaScript class.'
320 - )
321 - expect(console.error.calls.argsFor(2)[0]).toContain(
322 - 'propTypes was defined as an instance property on Foo.'
323 - )
324 - expect(console.error.calls.argsFor(3)[0]).toContain(
325 - 'contextTypes was defined as an instance property on Foo.'
326 - )
310 undefined
311
312 it 'does not warn about getInitialState() on class components
313 if state is also defined.', ->
331 - spyOnDev console, 'error'
314 class Foo extends React.Component
315 constructor: (props) ->
316 super props
@@ -342,12 +324,9 @@ describe 'ReactCoffeeScriptClass', ->
324 className: 'foo'
325
326 test React.createElement(Foo), 'SPAN', 'foo'
345 - if __DEV__
346 - expect(console.error.calls.count()).toBe 0
327 undefined
328
329 it 'should warn when misspelling shouldComponentUpdate', ->
350 - spyOnDev console, 'error'
330 class NamedComponent extends React.Component
331 componentShouldUpdate: ->
332 false
@@ -356,18 +335,16 @@ describe 'ReactCoffeeScriptClass', ->
335 span
336 className: 'foo'
337
359 - test React.createElement(NamedComponent), 'SPAN', 'foo'
360 - if __DEV__
361 - expect(console.error.calls.count()).toBe 1
362 - expect(console.error.calls.argsFor(0)[0]).toBe(
363 - 'Warning: NamedComponent has a method called componentShouldUpdate().
364 - Did you mean shouldComponentUpdate()? The name is phrased as a
365 - question because the function is expected to return a value.'
366 - )
338 + expect(->
339 + test React.createElement(NamedComponent), 'SPAN', 'foo'
340 + ).toWarnDev(
341 + 'Warning: NamedComponent has a method called componentShouldUpdate().
342 + Did you mean shouldComponentUpdate()? The name is phrased as a
343 + question because the function is expected to return a value.'
344 + )
345 undefined
346
347 it 'should warn when misspelling componentWillReceiveProps', ->
370 - spyOnDev console, 'error'
348 class NamedComponent extends React.Component
349 componentWillRecieveProps: ->
350 false
@@ -376,29 +353,27 @@ describe 'ReactCoffeeScriptClass', ->
353 span
354 className: 'foo'
355
379 - test React.createElement(NamedComponent), 'SPAN', 'foo'
380 - if __DEV__
381 - expect(console.error.calls.count()).toBe 1
382 - expect(console.error.calls.argsFor(0)[0]).toBe(
383 - 'Warning: NamedComponent has a method called componentWillRecieveProps().
384 - Did you mean componentWillReceiveProps()?'
385 - )
356 + expect(->
357 + test React.createElement(NamedComponent), 'SPAN', 'foo'
358 + ).toWarnDev(
359 + 'Warning: NamedComponent has a method called componentWillRecieveProps().
360 + Did you mean componentWillReceiveProps()?'
361 + )
362 undefined
363
364 it 'should throw AND warn when trying to access classic APIs', ->
389 - spyOnDev console, 'warn'
365 instance =
366 test Inner(name: 'foo'), 'DIV', 'foo'
392 - expect(-> instance.replaceState {}).toThrow()
393 - expect(-> instance.isMounted()).toThrow()
394 - if __DEV__
395 - expect(console.warn.calls.count()).toBe 2
396 - expect(console.warn.calls.argsFor(0)[0]).toContain(
397 - 'replaceState(...) is deprecated in plain JavaScript React classes'
398 - )
399 - expect(console.warn.calls.argsFor(1)[0]).toContain(
400 - 'isMounted(...) is deprecated in plain JavaScript React classes'
401 - )
367 + expect(->
368 + expect(-> instance.replaceState {}).toThrow()
369 + ).toLowPriorityWarnDev(
370 + 'replaceState(...) is deprecated in plain JavaScript React classes'
371 + )
372 + expect(->
373 + expect(-> instance.isMounted()).toThrow()
374 + ).toLowPriorityWarnDev(
375 + 'isMounted(...) is deprecated in plain JavaScript React classes'
376 + )
377 undefined
378
379 it 'supports this.context passed via getChildContext', ->
packages/react/src/__tests__/ReactContextValidator-test.js
+55 -90
@@ -21,10 +21,6 @@ let ReactDOM;
21 let ReactTestUtils;
22
23 describe('ReactContextValidator', () => {
24 - function normalizeCodeLocInfo(str) {
25 - return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
26 - }
27 -
24 beforeEach(() => {
25 jest.resetModules();
26
@@ -161,8 +157,6 @@ describe('ReactContextValidator', () => {
157 });
158
159 it('should check context types', () => {
164 - spyOnDev(console, 'error');
165 -
160 class Component extends React.Component {
161 render() {
162 return <div />;
@@ -172,17 +166,12 @@ describe('ReactContextValidator', () => {
166 foo: PropTypes.string.isRequired,
167 };
168
175 - ReactTestUtils.renderIntoDocument(<Component />);
176 -
177 - if (__DEV__) {
178 - expect(console.error.calls.count()).toBe(1);
179 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
180 - 'Warning: Failed context type: ' +
181 - 'The context `foo` is marked as required in `Component`, but its value ' +
182 - 'is `undefined`.\n' +
183 - ' in Component (at **)',
184 - );
185 - }
169 + expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
170 + 'Warning: Failed context type: ' +
171 + 'The context `foo` is marked as required in `Component`, but its value ' +
172 + 'is `undefined`.\n' +
173 + ' in Component (at **)',
174 + );
175
176 class ComponentInFooStringContext extends React.Component {
177 getChildContext() {
@@ -199,15 +188,11 @@ describe('ReactContextValidator', () => {
188 foo: PropTypes.string,
189 };
190
191 + // No additional errors expected
192 ReactTestUtils.renderIntoDocument(
193 <ComponentInFooStringContext fooValue={'bar'} />,
194 );
195
206 - // Previous call should not error
207 - if (__DEV__) {
208 - expect(console.error.calls.count()).toBe(1);
209 - }
210 -
196 class ComponentInFooNumberContext extends React.Component {
197 getChildContext() {
198 return {
@@ -223,25 +208,20 @@ describe('ReactContextValidator', () => {
208 foo: PropTypes.number,
209 };
210
226 - ReactTestUtils.renderIntoDocument(
227 - <ComponentInFooNumberContext fooValue={123} />,
211 + expect(() =>
212 + ReactTestUtils.renderIntoDocument(
213 + <ComponentInFooNumberContext fooValue={123} />,
214 + ),
215 + ).toWarnDev(
216 + 'Warning: Failed context type: ' +
217 + 'Invalid context `foo` of type `number` supplied ' +
218 + 'to `Component`, expected `string`.\n' +
219 + ' in Component (at **)\n' +
220 + ' in ComponentInFooNumberContext (at **)',
221 );
229 -
230 - if (__DEV__) {
231 - expect(console.error.calls.count()).toBe(2);
232 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
233 - 'Warning: Failed context type: ' +
234 - 'Invalid context `foo` of type `number` supplied ' +
235 - 'to `Component`, expected `string`.\n' +
236 - ' in Component (at **)\n' +
237 - ' in ComponentInFooNumberContext (at **)',
238 - );
239 - }
222 });
223
224 it('should check child context types', () => {
243 - spyOnDev(console, 'error');
244 -
225 class Component extends React.Component {
226 getChildContext() {
227 return this.props.testContext;
@@ -256,46 +236,35 @@ describe('ReactContextValidator', () => {
236 bar: PropTypes.number,
237 };
238
259 - ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
260 - if (__DEV__) {
261 - expect(console.error.calls.count()).toBe(1);
262 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
263 - 'Warning: Failed child context type: ' +
264 - 'The child context `foo` is marked as required in `Component`, but its ' +
265 - 'value is `undefined`.\n' +
266 - ' in Component (at **)',
267 - );
268 - }
269 -
270 - ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
239 + expect(() =>
240 + ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />),
241 + ).toWarnDev(
242 + 'Warning: Failed child context type: ' +
243 + 'The child context `foo` is marked as required in `Component`, but its ' +
244 + 'value is `undefined`.\n' +
245 + ' in Component (at **)',
246 + );
247
272 - if (__DEV__) {
273 - expect(console.error.calls.count()).toBe(2);
274 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
275 - 'Warning: Failed child context type: ' +
276 - 'Invalid child context `foo` of type `number` ' +
277 - 'supplied to `Component`, expected `string`.\n' +
278 - ' in Component (at **)',
279 - );
280 - }
248 + expect(() =>
249 + ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />),
250 + ).toWarnDev(
251 + 'Warning: Failed child context type: ' +
252 + 'Invalid child context `foo` of type `number` ' +
253 + 'supplied to `Component`, expected `string`.\n' +
254 + ' in Component (at **)',
255 + );
256
257 + // No additional errors expected
258 ReactTestUtils.renderIntoDocument(
259 <Component testContext={{foo: 'foo', bar: 123}} />,
260 );
261
262 ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 'foo'}} />);
287 -
288 - if (__DEV__) {
289 - // Previous calls should not log errors
290 - expect(console.error.calls.count()).toBe(2);
291 - }
263 });
264
265 // TODO (bvaughn) Remove this test and the associated behavior in the future.
266 // It has only been added in Fiber to match the (unintentional) behavior in Stack.
267 it('should warn (but not error) if getChildContext method is missing', () => {
297 - spyOnDev(console, 'error');
298 -
268 class ComponentA extends React.Component {
269 static childContextTypes = {
270 foo: PropTypes.string.isRequired,
@@ -313,40 +282,28 @@ describe('ReactContextValidator', () => {
282 }
283 }
284
316 - ReactTestUtils.renderIntoDocument(<ComponentA />);
317 - if (__DEV__) {
318 - expect(console.error.calls.count()).toBe(1);
319 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
320 - 'Warning: ComponentA.childContextTypes is specified but there is no ' +
321 - 'getChildContext() method on the instance. You can either define ' +
322 - 'getChildContext() on ComponentA or remove childContextTypes from it.',
323 - );
324 - }
285 + expect(() => ReactTestUtils.renderIntoDocument(<ComponentA />)).toWarnDev(
286 + 'Warning: ComponentA.childContextTypes is specified but there is no ' +
287 + 'getChildContext() method on the instance. You can either define ' +
288 + 'getChildContext() on ComponentA or remove childContextTypes from it.',
289 + );
290
291 // Warnings should be deduped by component type
292 ReactTestUtils.renderIntoDocument(<ComponentA />);
328 - if (__DEV__) {
329 - expect(console.error.calls.count()).toBe(1);
330 - }
331 - ReactTestUtils.renderIntoDocument(<ComponentB />);
332 - if (__DEV__) {
333 - expect(console.error.calls.count()).toBe(2);
334 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
335 - 'Warning: ComponentB.childContextTypes is specified but there is no ' +
336 - 'getChildContext() method on the instance. You can either define ' +
337 - 'getChildContext() on ComponentB or remove childContextTypes from it.',
338 - );
339 - }
293 +
294 + expect(() => ReactTestUtils.renderIntoDocument(<ComponentB />)).toWarnDev(
295 + 'Warning: ComponentB.childContextTypes is specified but there is no ' +
296 + 'getChildContext() method on the instance. You can either define ' +
297 + 'getChildContext() on ComponentB or remove childContextTypes from it.',
298 + );
299 });
300
301 // TODO (bvaughn) Remove this test and the associated behavior in the future.
302 // It has only been added in Fiber to match the (unintentional) behavior in Stack.
303 it('should pass parent context if getChildContext method is missing', () => {
345 - spyOnDev(console, 'error');
346 -
304 class ParentContextProvider extends React.Component {
305 static childContextTypes = {
349 - foo: PropTypes.number,
306 + foo: PropTypes.string,
307 };
308 getChildContext() {
309 return {
@@ -379,7 +336,15 @@ describe('ReactContextValidator', () => {
336 foo: PropTypes.string.isRequired,
337 };
338
382 - ReactTestUtils.renderIntoDocument(<ParentContextProvider />);
339 + expect(() =>
340 + ReactTestUtils.renderIntoDocument(<ParentContextProvider />),
341 + ).toWarnDev([
342 + 'Warning: MiddleMissingContext.childContextTypes is specified but there is no getChildContext() method on the ' +
343 + 'instance. You can either define getChildContext() on MiddleMissingContext or remove childContextTypes from ' +
344 + 'it.',
345 + 'Warning: Failed context type: The context `bar` is marked as required in `ChildContextConsumer`, but its ' +
346 + 'value is `undefined`.',
347 + ]);
348 expect(childContext.bar).toBeUndefined();
349 expect(childContext.foo).toBe('FOO');
350 });
packages/react/src/__tests__/ReactES6Class-test.js
+37 -75
@@ -56,17 +56,13 @@ describe('ReactES6Class', () => {
56 });
57
58 it('throws if no render function is defined', () => {
59 - spyOnDev(console, 'error');
59 class Foo extends React.Component {}
61 - expect(() => ReactDOM.render(<Foo />, container)).toThrow();
62 -
63 - if (__DEV__) {
64 - expect(console.error.calls.count()).toBe(1);
65 - expect(console.error.calls.argsFor(0)[0]).toBe(
66 - 'Warning: Foo(...): No `render` method found on the returned component ' +
67 - 'instance: you may have forgotten to define `render`.',
68 - );
69 - }
60 + expect(() =>
61 + expect(() => ReactDOM.render(<Foo />, container)).toThrow(),
62 + ).toWarnDev(
63 + 'Warning: Foo(...): No `render` method found on the returned component ' +
64 + 'instance: you may have forgotten to define `render`.',
65 + );
66 });
67
68 it('renders a simple stateless component with prop', () => {
@@ -164,7 +160,6 @@ describe('ReactES6Class', () => {
160 });
161
162 it('should warn with non-object in the initial state property', () => {
167 - spyOnDev(console, 'error');
163 [['an array'], 'a string', 1234].forEach(function(state) {
164 class Foo extends React.Component {
165 constructor() {
@@ -175,14 +170,9 @@ describe('ReactES6Class', () => {
170 return <span />;
171 }
172 }
178 - test(<Foo />, 'SPAN', '');
179 - if (__DEV__) {
180 - expect(console.error.calls.count()).toBe(1);
181 - expect(console.error.calls.argsFor(0)[0]).toContain(
182 - 'Foo.state: must be set to an object or null',
183 - );
184 - console.error.calls.reset();
185 - }
173 + expect(() => test(<Foo />, 'SPAN', '')).toWarnDev(
174 + 'Foo.state: must be set to an object or null',
175 + );
176 });
177 });
178
@@ -310,7 +300,6 @@ describe('ReactES6Class', () => {
300 });
301
302 it('warns when classic properties are defined on the instance, but does not invoke them.', () => {
313 - spyOnDev(console, 'error');
303 let getDefaultPropsWasCalled = false;
304 let getInitialStateWasCalled = false;
305 class Foo extends React.Component {
@@ -331,28 +320,18 @@ describe('ReactES6Class', () => {
320 return <span className="foo" />;
321 }
322 }
334 - test(<Foo />, 'SPAN', 'foo');
323 +
324 + expect(() => test(<Foo />, 'SPAN', 'foo')).toWarnDev([
325 + 'getInitialState was defined on Foo, a plain JavaScript class.',
326 + 'getDefaultProps was defined on Foo, a plain JavaScript class.',
327 + 'propTypes was defined as an instance property on Foo.',
328 + 'contextTypes was defined as an instance property on Foo.',
329 + ]);
330 expect(getInitialStateWasCalled).toBe(false);
331 expect(getDefaultPropsWasCalled).toBe(false);
337 - if (__DEV__) {
338 - expect(console.error.calls.count()).toBe(4);
339 - expect(console.error.calls.argsFor(0)[0]).toContain(
340 - 'getInitialState was defined on Foo, a plain JavaScript class.',
341 - );
342 - expect(console.error.calls.argsFor(1)[0]).toContain(
343 - 'getDefaultProps was defined on Foo, a plain JavaScript class.',
344 - );
345 - expect(console.error.calls.argsFor(2)[0]).toContain(
346 - 'propTypes was defined as an instance property on Foo.',
347 - );
348 - expect(console.error.calls.argsFor(3)[0]).toContain(
349 - 'contextTypes was defined as an instance property on Foo.',
350 - );
351 - }
332 });
333
334 it('does not warn about getInitialState() on class components if state is also defined.', () => {
355 - spyOnDev(console, 'error');
335 class Foo extends React.Component {
336 state = this.getInitialState();
337 getInitialState() {
@@ -363,14 +342,9 @@ describe('ReactES6Class', () => {
342 }
343 }
344 test(<Foo />, 'SPAN', 'foo');
366 - if (__DEV__) {
367 - expect(console.error.calls.count()).toBe(0);
368 - }
345 });
346
347 it('should warn when misspelling shouldComponentUpdate', () => {
372 - spyOnDev(console, 'error');
373 -
348 class NamedComponent extends React.Component {
349 componentShouldUpdate() {
350 return false;
@@ -379,22 +353,16 @@ describe('ReactES6Class', () => {
353 return <span className="foo" />;
354 }
355 }
382 - test(<NamedComponent />, 'SPAN', 'foo');
356
384 - if (__DEV__) {
385 - expect(console.error.calls.count()).toBe(1);
386 - expect(console.error.calls.argsFor(0)[0]).toBe(
387 - 'Warning: ' +
388 - 'NamedComponent has a method called componentShouldUpdate(). Did you ' +
389 - 'mean shouldComponentUpdate()? The name is phrased as a question ' +
390 - 'because the function is expected to return a value.',
391 - );
392 - }
357 + expect(() => test(<NamedComponent />, 'SPAN', 'foo')).toWarnDev(
358 + 'Warning: ' +
359 + 'NamedComponent has a method called componentShouldUpdate(). Did you ' +
360 + 'mean shouldComponentUpdate()? The name is phrased as a question ' +
361 + 'because the function is expected to return a value.',
362 + );
363 });
364
365 it('should warn when misspelling componentWillReceiveProps', () => {
396 - spyOnDev(console, 'error');
397 -
366 class NamedComponent extends React.Component {
367 componentWillRecieveProps() {
368 return false;
@@ -403,32 +371,26 @@ describe('ReactES6Class', () => {
371 return <span className="foo" />;
372 }
373 }
406 - test(<NamedComponent />, 'SPAN', 'foo');
374
408 - if (__DEV__) {
409 - expect(console.error.calls.count()).toBe(1);
410 - expect(console.error.calls.argsFor(0)[0]).toBe(
411 - 'Warning: ' +
412 - 'NamedComponent has a method called componentWillRecieveProps(). Did ' +
413 - 'you mean componentWillReceiveProps()?',
414 - );
415 - }
375 + expect(() => test(<NamedComponent />, 'SPAN', 'foo')).toWarnDev(
376 + 'Warning: ' +
377 + 'NamedComponent has a method called componentWillRecieveProps(). Did ' +
378 + 'you mean componentWillReceiveProps()?',
379 + );
380 });
381
382 it('should throw AND warn when trying to access classic APIs', () => {
419 - spyOnDev(console, 'warn');
383 const instance = test(<Inner name="foo" />, 'DIV', 'foo');
421 - expect(() => instance.replaceState({})).toThrow();
422 - expect(() => instance.isMounted()).toThrow();
423 - if (__DEV__) {
424 - expect(console.warn.calls.count()).toBe(2);
425 - expect(console.warn.calls.argsFor(0)[0]).toContain(
426 - 'replaceState(...) is deprecated in plain JavaScript React classes',
427 - );
428 - expect(console.warn.calls.argsFor(1)[0]).toContain(
429 - 'isMounted(...) is deprecated in plain JavaScript React classes',
430 - );
431 - }
384 + expect(() =>
385 + expect(() => instance.replaceState({})).toThrow(),
386 + ).toLowPriorityWarnDev(
387 + 'replaceState(...) is deprecated in plain JavaScript React classes',
388 + );
389 + expect(() =>
390 + expect(() => instance.isMounted()).toThrow(),
391 + ).toLowPriorityWarnDev(
392 + 'isMounted(...) is deprecated in plain JavaScript React classes',
393 + );
394 });
395
396 it('supports this.context passed via getChildContext', () => {
packages/react/src/__tests__/ReactElement-test.js
+18 -42
@@ -58,7 +58,6 @@ describe('ReactElement', () => {
58 });
59
60 it('should warn when `key` is being accessed on composite element', () => {
61 - spyOnDev(console, 'error');
61 const container = document.createElement('div');
62 class Child extends React.Component {
63 render() {
@@ -76,41 +75,25 @@ describe('ReactElement', () => {
75 );
76 }
77 }
79 - if (__DEV__) {
80 - expect(console.error.calls.count()).toBe(0);
81 - }
82 - ReactDOM.render(<Parent />, container);
83 - if (__DEV__) {
84 - expect(console.error.calls.count()).toBe(1);
85 - expect(console.error.calls.argsFor(0)[0]).toContain(
86 - 'Child: `key` is not a prop. Trying to access it will result ' +
87 - 'in `undefined` being returned. If you need to access the same ' +
88 - 'value within the child component, you should pass it as a different ' +
89 - 'prop. (https://fb.me/react-special-props)',
90 - );
91 - }
78 + expect(() => ReactDOM.render(<Parent />, container)).toWarnDev(
79 + 'Child: `key` is not a prop. Trying to access it will result ' +
80 + 'in `undefined` being returned. If you need to access the same ' +
81 + 'value within the child component, you should pass it as a different ' +
82 + 'prop. (https://fb.me/react-special-props)',
83 + );
84 });
85
86 it('should warn when `key` is being accessed on a host element', () => {
95 - spyOnDev(console, 'error');
87 const element = <div key="3" />;
97 - if (__DEV__) {
98 - expect(console.error.calls.count()).toBe(0);
99 - }
100 - void element.props.key;
101 - if (__DEV__) {
102 - expect(console.error.calls.count()).toBe(1);
103 - expect(console.error.calls.argsFor(0)[0]).toContain(
104 - 'div: `key` is not a prop. Trying to access it will result ' +
105 - 'in `undefined` being returned. If you need to access the same ' +
106 - 'value within the child component, you should pass it as a different ' +
107 - 'prop. (https://fb.me/react-special-props)',
108 - );
109 - }
88 + expect(() => void element.props.key).toWarnDev(
89 + 'div: `key` is not a prop. Trying to access it will result ' +
90 + 'in `undefined` being returned. If you need to access the same ' +
91 + 'value within the child component, you should pass it as a different ' +
92 + 'prop. (https://fb.me/react-special-props)',
93 + );
94 });
95
96 it('should warn when `ref` is being accessed', () => {
113 - spyOnDev(console, 'error');
97 const container = document.createElement('div');
98 class Child extends React.Component {
99 render() {
@@ -126,19 +109,12 @@ describe('ReactElement', () => {
109 );
110 }
111 }
129 - if (__DEV__) {
130 - expect(console.error.calls.count()).toBe(0);
131 - }
132 - ReactDOM.render(<Parent />, container);
133 - if (__DEV__) {
134 - expect(console.error.calls.count()).toBe(1);
135 - expect(console.error.calls.argsFor(0)[0]).toContain(
136 - 'Child: `ref` is not a prop. Trying to access it will result ' +
137 - 'in `undefined` being returned. If you need to access the same ' +
138 - 'value within the child component, you should pass it as a different ' +
139 - 'prop. (https://fb.me/react-special-props)',
140 - );
141 - }
112 + expect(() => ReactDOM.render(<Parent />, container)).toWarnDev(
113 + 'Child: `ref` is not a prop. Trying to access it will result ' +
114 + 'in `undefined` being returned. If you need to access the same ' +
115 + 'value within the child component, you should pass it as a different ' +
116 + 'prop. (https://fb.me/react-special-props)',
117 + );
118 });
119
120 it('allows a string to be passed as the type', () => {
packages/react/src/__tests__/ReactElementClone-test.js
+15 -23
@@ -257,16 +257,11 @@ describe('ReactElementClone', () => {
257 });
258
259 it('warns for keys for arrays of elements in rest args', () => {
260 - spyOnDev(console, 'error');
261 -
262 - React.cloneElement(<div />, null, [<div />, <div />]);
263 -
264 - if (__DEV__) {
265 - expect(console.error.calls.count()).toBe(1);
266 - expect(console.error.calls.argsFor(0)[0]).toContain(
267 - 'Each child in an array or iterator should have a unique "key" prop.',
268 - );
269 - }
260 + expect(() =>
261 + React.cloneElement(<div />, null, [<div />, <div />]),
262 + ).toWarnDev(
263 + 'Each child in an array or iterator should have a unique "key" prop.',
264 + );
265 });
266
267 it('does not warns for arrays of elements with keys', () => {
@@ -282,7 +277,6 @@ describe('ReactElementClone', () => {
277 });
278
279 it('should check declared prop types after clone', () => {
285 - spyOnDev(console, 'error');
280 class Component extends React.Component {
281 static propTypes = {
282 color: PropTypes.string.isRequired,
@@ -303,18 +297,16 @@ describe('ReactElementClone', () => {
297 });
298 }
299 }
306 - ReactTestUtils.renderIntoDocument(React.createElement(GrandParent));
307 - if (__DEV__) {
308 - expect(console.error.calls.count()).toBe(1);
309 - expect(console.error.calls.argsFor(0)[0]).toBe(
310 - 'Warning: Failed prop type: ' +
311 - 'Invalid prop `color` of type `number` supplied to `Component`, ' +
312 - 'expected `string`.\n' +
313 - ' in Component (created by GrandParent)\n' +
314 - ' in Parent (created by GrandParent)\n' +
315 - ' in GrandParent',
316 - );
317 - }
300 + expect(() =>
301 + ReactTestUtils.renderIntoDocument(React.createElement(GrandParent)),
302 + ).toWarnDev(
303 + 'Warning: Failed prop type: ' +
304 + 'Invalid prop `color` of type `number` supplied to `Component`, ' +
305 + 'expected `string`.\n' +
306 + ' in Component (created by GrandParent)\n' +
307 + ' in Parent (created by GrandParent)\n' +
308 + ' in GrandParent',
309 + );
310 });
311
312 it('should ignore key and ref warning getters', () => {
packages/react/src/__tests__/ReactElementValidator-test.js
+157 -243
@@ -18,10 +18,6 @@ let ReactDOM;
18 let ReactTestUtils;
19
20 describe('ReactElementValidator', () => {
21 - function normalizeCodeLocInfo(str) {
22 - return str && str.replace(/at .+?:\d+/g, 'at **');
23 - }
24 -
21 let ComponentClass;
22
23 beforeEach(() => {
@@ -39,21 +35,16 @@ describe('ReactElementValidator', () => {
35 });
36
37 it('warns for keys for arrays of elements in rest args', () => {
42 - spyOnDev(console, 'error');
38 const Component = React.createFactory(ComponentClass);
39
45 - Component(null, [Component(), Component()]);
46 -
47 - if (__DEV__) {
48 - expect(console.error.calls.count()).toBe(1);
49 - expect(console.error.calls.argsFor(0)[0]).toContain(
50 - 'Each child in an array or iterator should have a unique "key" prop.',
51 - );
52 - }
40 + expect(() => {
41 + Component(null, [Component(), Component()]);
42 + }).toWarnDev(
43 + 'Each child in an array or iterator should have a unique "key" prop.',
44 + );
45 });
46
47 it('warns for keys for arrays of elements with owner info', () => {
56 - spyOnDev(console, 'error');
48 const Component = React.createFactory(ComponentClass);
49
50 class InnerClass extends React.Component {
@@ -70,59 +61,46 @@ describe('ReactElementValidator', () => {
61 }
62 }
63
73 - ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper));
74 -
75 - if (__DEV__) {
76 - expect(console.error.calls.count()).toBe(1);
77 - expect(console.error.calls.argsFor(0)[0]).toContain(
78 - 'Each child in an array or iterator should have a unique "key" prop.' +
79 - '\n\nCheck the render method of `InnerClass`. ' +
80 - 'It was passed a child from ComponentWrapper. ',
81 - );
82 - }
64 + expect(() => {
65 + ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper));
66 + }).toWarnDev(
67 + 'Each child in an array or iterator should have a unique "key" prop.' +
68 + '\n\nCheck the render method of `InnerClass`. ' +
69 + 'It was passed a child from ComponentWrapper. ',
70 + );
71 });
72
73 it('warns for keys for arrays with no owner or parent info', () => {
86 - spyOnDev(console, 'error');
87 -
74 function Anonymous() {
75 return <div />;
76 }
77 Object.defineProperty(Anonymous, 'name', {value: undefined});
78
79 const divs = [<div />, <div />];
94 - ReactTestUtils.renderIntoDocument(<Anonymous>{divs}</Anonymous>);
95 -
96 - if (__DEV__) {
97 - expect(console.error.calls.count()).toBe(1);
98 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
99 - 'Warning: Each child in an array or iterator should have a unique ' +
100 - '"key" prop. See https://fb.me/react-warning-keys for more information.\n' +
101 - ' in div (at **)',
102 - );
103 - }
80 +
81 + expect(() => {
82 + ReactTestUtils.renderIntoDocument(<Anonymous>{divs}</Anonymous>);
83 + }).toWarnDev(
84 + 'Warning: Each child in an array or iterator should have a unique ' +
85 + '"key" prop. See https://fb.me/react-warning-keys for more information.\n' +
86 + ' in div (at **)',
87 + );
88 });
89
90 it('warns for keys for arrays of elements with no owner info', () => {
107 - spyOnDev(console, 'error');
108 -
91 const divs = [<div />, <div />];
110 - ReactTestUtils.renderIntoDocument(<div>{divs}</div>);
111 -
112 - if (__DEV__) {
113 - expect(console.error.calls.count()).toBe(1);
114 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
115 - 'Warning: Each child in an array or iterator should have a unique ' +
116 - '"key" prop.\n\nCheck the top-level render call using <div>. See ' +
117 - 'https://fb.me/react-warning-keys for more information.\n' +
118 - ' in div (at **)',
119 - );
120 - }
92 +
93 + expect(() => {
94 + ReactTestUtils.renderIntoDocument(<div>{divs}</div>);
95 + }).toWarnDev(
96 + 'Warning: Each child in an array or iterator should have a unique ' +
97 + '"key" prop.\n\nCheck the top-level render call using <div>. See ' +
98 + 'https://fb.me/react-warning-keys for more information.\n' +
99 + ' in div (at **)',
100 + );
101 });
102
103 it('warns for keys with component stack info', () => {
124 - spyOnDev(console, 'error');
125 -
104 function Component() {
105 return <div>{[<div />, <div />]}</div>;
106 }
@@ -135,20 +113,15 @@ describe('ReactElementValidator', () => {
113 return <Parent child={<Component />} />;
114 }
115
138 - ReactTestUtils.renderIntoDocument(<GrandParent />);
139 -
140 - if (__DEV__) {
141 - expect(console.error.calls.count()).toBe(1);
142 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
143 - 'Warning: Each child in an array or iterator should have a unique ' +
144 - '"key" prop.\n\nCheck the render method of `Component`. See ' +
145 - 'https://fb.me/react-warning-keys for more information.\n' +
146 - ' in div (at **)\n' +
147 - ' in Component (at **)\n' +
148 - ' in Parent (at **)\n' +
149 - ' in GrandParent (at **)',
150 - );
151 - }
116 + expect(() => ReactTestUtils.renderIntoDocument(<GrandParent />)).toWarnDev(
117 + 'Warning: Each child in an array or iterator should have a unique ' +
118 + '"key" prop.\n\nCheck the render method of `Component`. See ' +
119 + 'https://fb.me/react-warning-keys for more information.\n' +
120 + ' in div (at **)\n' +
121 + ' in Component (at **)\n' +
122 + ' in Parent (at **)\n' +
123 + ' in GrandParent (at **)',
124 + );
125 });
126
127 it('does not warn for keys when passing children down', () => {
@@ -170,7 +143,6 @@ describe('ReactElementValidator', () => {
143 });
144
145 it('warns for keys for iterables of elements in rest args', () => {
173 - spyOnDev(console, 'error');
146 const Component = React.createFactory(ComponentClass);
147
148 const iterable = {
@@ -185,14 +157,9 @@ describe('ReactElementValidator', () => {
157 },
158 };
159
188 - Component(null, iterable);
189 -
190 - if (__DEV__) {
191 - expect(console.error.calls.count()).toBe(1);
192 - expect(console.error.calls.argsFor(0)[0]).toContain(
193 - 'Each child in an array or iterator should have a unique "key" prop.',
194 - );
195 - }
160 + expect(() => Component(null, iterable)).toWarnDev(
161 + 'Each child in an array or iterator should have a unique "key" prop.',
162 + );
163 });
164
165 it('does not warns for arrays of elements with keys', () => {
@@ -238,7 +205,6 @@ describe('ReactElementValidator', () => {
205 // In this test, we're making sure that if a proptype error is found in a
206 // component, we give a small hint as to which parent instantiated that
207 // component as per warnings about key usage in ReactElementValidator.
241 - spyOnDev(console, 'error');
208 function MyComp(props) {
209 return React.createElement('div', null, 'My color is ' + props.color);
210 }
@@ -248,89 +214,72 @@ describe('ReactElementValidator', () => {
214 function ParentComp() {
215 return React.createElement(MyComp, {color: 123});
216 }
251 - ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
252 - if (__DEV__) {
253 - expect(console.error.calls.argsFor(0)[0]).toBe(
254 - 'Warning: Failed prop type: ' +
255 - 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
256 - 'expected `string`.\n' +
257 - ' in MyComp (created by ParentComp)\n' +
258 - ' in ParentComp',
259 - );
260 - }
217 + expect(() => {
218 + ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
219 + }).toWarnDev(
220 + 'Warning: Failed prop type: ' +
221 + 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
222 + 'expected `string`.\n' +
223 + ' in MyComp (created by ParentComp)\n' +
224 + ' in ParentComp',
225 + );
226 });
227
228 it('gives a helpful error when passing invalid types', () => {
264 - spyOnDev(console, 'error');
265 - React.createElement(undefined);
266 - React.createElement(null);
267 - React.createElement(true);
268 - React.createElement({x: 17});
269 - React.createElement({});
270 - if (__DEV__) {
271 - expect(console.error.calls.count()).toBe(5);
272 - expect(console.error.calls.argsFor(0)[0]).toBe(
273 - 'Warning: React.createElement: type is invalid -- expected a string ' +
274 - '(for built-in components) or a class/function (for composite ' +
275 - 'components) but got: undefined. You likely forgot to export your ' +
276 - "component from the file it's defined in, or you might have mixed up " +
277 - 'default and named imports.',
278 - );
279 - expect(console.error.calls.argsFor(1)[0]).toBe(
280 - 'Warning: React.createElement: type is invalid -- expected a string ' +
281 - '(for built-in components) or a class/function (for composite ' +
282 - 'components) but got: null.',
283 - );
284 - expect(console.error.calls.argsFor(2)[0]).toBe(
285 - 'Warning: React.createElement: type is invalid -- expected a string ' +
286 - '(for built-in components) or a class/function (for composite ' +
287 - 'components) but got: boolean.',
288 - );
289 - expect(console.error.calls.argsFor(3)[0]).toBe(
290 - 'Warning: React.createElement: type is invalid -- expected a string ' +
291 - '(for built-in components) or a class/function (for composite ' +
292 - 'components) but got: object.',
293 - );
294 - expect(console.error.calls.argsFor(4)[0]).toBe(
295 - 'Warning: React.createElement: type is invalid -- expected a string ' +
296 - '(for built-in components) or a class/function (for composite ' +
297 - 'components) but got: object. You likely forgot to export your ' +
298 - "component from the file it's defined in, or you might have mixed up " +
299 - 'default and named imports.',
300 - );
301 - }
229 + expect(() => {
230 + React.createElement(undefined);
231 + React.createElement(null);
232 + React.createElement(true);
233 + React.createElement({x: 17});
234 + React.createElement({});
235 + }).toWarnDev([
236 + 'Warning: React.createElement: type is invalid -- expected a string ' +
237 + '(for built-in components) or a class/function (for composite ' +
238 + 'components) but got: undefined. You likely forgot to export your ' +
239 + "component from the file it's defined in, or you might have mixed up " +
240 + 'default and named imports.',
241 + 'Warning: React.createElement: type is invalid -- expected a string ' +
242 + '(for built-in components) or a class/function (for composite ' +
243 + 'components) but got: null.',
244 + 'Warning: React.createElement: type is invalid -- expected a string ' +
245 + '(for built-in components) or a class/function (for composite ' +
246 + 'components) but got: boolean.',
247 + 'Warning: React.createElement: type is invalid -- expected a string ' +
248 + '(for built-in components) or a class/function (for composite ' +
249 + 'components) but got: object.',
250 + 'Warning: React.createElement: type is invalid -- expected a string ' +
251 + '(for built-in components) or a class/function (for composite ' +
252 + 'components) but got: object. You likely forgot to export your ' +
253 + "component from the file it's defined in, or you might have mixed up " +
254 + 'default and named imports.',
255 + ]);
256 +
257 + // Should not log any additional warnings
258 React.createElement('div');
303 - if (__DEV__) {
304 - expect(console.error.calls.count()).toBe(5);
305 - }
259 });
260
261 it('includes the owner name when passing null, undefined, boolean, or number', () => {
309 - spyOnDev(console, 'error');
262 function ParentComp() {
263 return React.createElement(null);
264 }
313 - expect(function() {
314 - ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
315 - }).toThrowError(
316 - 'Element type is invalid: expected a string (for built-in components) ' +
317 - 'or a class/function (for composite components) but got: null.' +
318 - (__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
319 - );
320 - if (__DEV__) {
321 - expect(console.error.calls.count()).toBe(1);
322 - expect(console.error.calls.argsFor(0)[0]).toBe(
323 - 'Warning: React.createElement: type is invalid -- expected a string ' +
324 - '(for built-in components) or a class/function (for composite ' +
325 - 'components) but got: null.' +
326 - '\n\nCheck the render method of `ParentComp`.\n in ParentComp',
265 +
266 + expect(() => {
267 + expect(() => {
268 + ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
269 + }).toThrowError(
270 + 'Element type is invalid: expected a string (for built-in components) ' +
271 + 'or a class/function (for composite components) but got: null.' +
272 + (__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
273 );
328 - }
274 + }).toWarnDev(
275 + 'Warning: React.createElement: type is invalid -- expected a string ' +
276 + '(for built-in components) or a class/function (for composite ' +
277 + 'components) but got: null.' +
278 + '\n\nCheck the render method of `ParentComp`.\n in ParentComp',
279 + );
280 });
281
282 it('should check default prop values', () => {
332 - spyOnDev(console, 'error');
333 -
283 class Component extends React.Component {
284 static propTypes = {prop: PropTypes.string.isRequired};
285 static defaultProps = {prop: null};
@@ -339,21 +288,16 @@ describe('ReactElementValidator', () => {
288 }
289 }
290
342 - ReactTestUtils.renderIntoDocument(React.createElement(Component));
343 -
344 - if (__DEV__) {
345 - expect(console.error.calls.count()).toBe(1);
346 - expect(console.error.calls.argsFor(0)[0]).toBe(
347 - 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
348 - '`Component`, but its value is `null`.\n' +
349 - ' in Component',
350 - );
351 - }
291 + expect(() =>
292 + ReactTestUtils.renderIntoDocument(React.createElement(Component)),
293 + ).toWarnDev(
294 + 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
295 + '`Component`, but its value is `null`.\n' +
296 + ' in Component',
297 + );
298 });
299
300 it('should not check the default for explicit null', () => {
355 - spyOnDev(console, 'error');
356 -
301 class Component extends React.Component {
302 static propTypes = {prop: PropTypes.string.isRequired};
303 static defaultProps = {prop: 'text'};
@@ -362,23 +306,18 @@ describe('ReactElementValidator', () => {
306 }
307 }
308
365 - ReactTestUtils.renderIntoDocument(
366 - React.createElement(Component, {prop: null}),
367 - );
368 -
369 - if (__DEV__) {
370 - expect(console.error.calls.count()).toBe(1);
371 - expect(console.error.calls.argsFor(0)[0]).toBe(
372 - 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
373 - '`Component`, but its value is `null`.\n' +
374 - ' in Component',
309 + expect(() => {
310 + ReactTestUtils.renderIntoDocument(
311 + React.createElement(Component, {prop: null}),
312 );
376 - }
313 + }).toWarnDev(
314 + 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
315 + '`Component`, but its value is `null`.\n' +
316 + ' in Component',
317 + );
318 });
319
320 it('should check declared prop types', () => {
380 - spyOnDev(console, 'error');
381 -
321 class Component extends React.Component {
322 static propTypes = {
323 prop: PropTypes.string.isRequired,
@@ -388,36 +327,26 @@ describe('ReactElementValidator', () => {
327 }
328 }
329
391 - ReactTestUtils.renderIntoDocument(React.createElement(Component));
392 - ReactTestUtils.renderIntoDocument(
393 - React.createElement(Component, {prop: 42}),
394 - );
395 -
396 - if (__DEV__) {
397 - expect(console.error.calls.count()).toBe(2);
398 - expect(console.error.calls.argsFor(0)[0]).toBe(
399 - 'Warning: Failed prop type: ' +
400 - 'The prop `prop` is marked as required in `Component`, but its value ' +
401 - 'is `undefined`.\n' +
402 - ' in Component',
403 - );
404 -
405 - expect(console.error.calls.argsFor(1)[0]).toBe(
406 - 'Warning: Failed prop type: ' +
407 - 'Invalid prop `prop` of type `number` supplied to ' +
408 - '`Component`, expected `string`.\n' +
409 - ' in Component',
330 + expect(() => {
331 + ReactTestUtils.renderIntoDocument(React.createElement(Component));
332 + ReactTestUtils.renderIntoDocument(
333 + React.createElement(Component, {prop: 42}),
334 );
411 - }
412 -
335 + }).toWarnDev([
336 + 'Warning: Failed prop type: ' +
337 + 'The prop `prop` is marked as required in `Component`, but its value ' +
338 + 'is `undefined`.\n' +
339 + ' in Component',
340 + 'Warning: Failed prop type: ' +
341 + 'Invalid prop `prop` of type `number` supplied to ' +
342 + '`Component`, expected `string`.\n' +
343 + ' in Component',
344 + ]);
345 +
346 + // Should not error for strings
347 ReactTestUtils.renderIntoDocument(
348 React.createElement(Component, {prop: 'string'}),
349 );
416 -
417 - if (__DEV__) {
418 - // Should not error for strings
419 - expect(console.error.calls.count()).toBe(2);
420 - }
350 });
351
352 it('should warn if a PropType creator is used as a PropType', () => {
@@ -432,24 +361,20 @@ describe('ReactElementValidator', () => {
361 }
362 }
363
435 - ReactTestUtils.renderIntoDocument(
436 - React.createElement(Component, {myProp: {value: 'hi'}}),
437 - );
438 -
439 - if (__DEV__) {
440 - expect(console.error.calls.count()).toBe(1);
441 - expect(console.error.calls.argsFor(0)[0]).toBe(
442 - 'Warning: Component: type specification of prop `myProp` is invalid; ' +
443 - 'the type checker function must return `null` or an `Error` but ' +
444 - 'returned a function. You may have forgotten to pass an argument to ' +
445 - 'the type checker creator (arrayOf, instanceOf, objectOf, oneOf, ' +
446 - 'oneOfType, and shape all require an argument).',
364 + expect(() => {
365 + ReactTestUtils.renderIntoDocument(
366 + React.createElement(Component, {myProp: {value: 'hi'}}),
367 );
448 - }
368 + }).toWarnDev(
369 + 'Warning: Component: type specification of prop `myProp` is invalid; ' +
370 + 'the type checker function must return `null` or an `Error` but ' +
371 + 'returned a function. You may have forgotten to pass an argument to ' +
372 + 'the type checker creator (arrayOf, instanceOf, objectOf, oneOf, ' +
373 + 'oneOfType, and shape all require an argument).',
374 + );
375 });
376
377 it('should warn if component declares PropTypes instead of propTypes', () => {
452 - spyOnDevAndProd(console, 'error');
378 class MisspelledPropTypesComponent extends React.Component {
379 static PropTypes = {
380 prop: PropTypes.string,
@@ -459,37 +384,29 @@ describe('ReactElementValidator', () => {
384 }
385 }
386
462 - ReactTestUtils.renderIntoDocument(
463 - React.createElement(MisspelledPropTypesComponent, {prop: 'Hi'}),
464 - );
465 - if (__DEV__) {
466 - expect(console.error.calls.count()).toBe(1);
467 - expect(console.error.calls.argsFor(0)[0]).toBe(
468 - 'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
469 - 'instead of `propTypes`. Did you misspell the property assignment?',
387 + expect(() => {
388 + ReactTestUtils.renderIntoDocument(
389 + React.createElement(MisspelledPropTypesComponent, {prop: 'Hi'}),
390 );
471 - }
391 + }).toWarnDev(
392 + 'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
393 + 'instead of `propTypes`. Did you misspell the property assignment?',
394 + );
395 });
396
397 it('should warn when accessing .type on an element factory', () => {
475 - spyOnDev(console, 'warn');
398 function TestComponent() {
399 return <div />;
400 }
479 - const TestFactory = React.createFactory(TestComponent);
480 - expect(TestFactory.type).toBe(TestComponent);
481 - if (__DEV__) {
482 - expect(console.warn.calls.count()).toBe(1);
483 - expect(console.warn.calls.argsFor(0)[0]).toBe(
484 - 'Warning: Factory.type is deprecated. Access the class directly before ' +
485 - 'passing it to createFactory.',
486 - );
487 - }
401 +
402 + let TestFactory = React.createFactory(TestComponent);
403 + expect(() => TestFactory.type).toLowPriorityWarnDev(
404 + 'Warning: Factory.type is deprecated. Access the class directly before ' +
405 + 'passing it to createFactory.',
406 + );
407 +
408 // Warn once, not again
409 expect(TestFactory.type).toBe(TestComponent);
490 - if (__DEV__) {
491 - expect(console.warn.calls.count()).toBe(1);
492 - }
410 });
411
412 it('does not warn when using DOM node as children', () => {
@@ -544,18 +461,15 @@ describe('ReactElementValidator', () => {
461 });
462
463 it('does not blow up on key warning with undefined type', () => {
547 - spyOnDev(console, 'error');
464 const Foo = undefined;
549 - void <Foo>{[<div />]}</Foo>;
550 - if (__DEV__) {
551 - expect(console.error.calls.count()).toBe(1);
552 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
553 - 'Warning: React.createElement: type is invalid -- expected a string ' +
554 - '(for built-in components) or a class/function (for composite ' +
555 - 'components) but got: undefined. You likely forgot to export your ' +
556 - "component from the file it's defined in, or you might have mixed up " +
557 - 'default and named imports.\n\nCheck your code at **.',
558 - );
559 - }
465 + expect(() => {
466 + void <Foo>{[<div />]}</Foo>;
467 + }).toWarnDev(
468 + 'Warning: React.createElement: type is invalid -- expected a string ' +
469 + '(for built-in components) or a class/function (for composite ' +
470 + 'components) but got: undefined. You likely forgot to export your ' +
471 + "component from the file it's defined in, or you might have mixed up " +
472 + 'default and named imports.\n\nCheck your code at **.',
473 + );
474 });
475 });
packages/react/src/__tests__/ReactJSXElementValidator-test.js
+129 -224
@@ -17,10 +17,6 @@ let ReactTestUtils;
17 let PropTypes;
18
19 describe('ReactJSXElementValidator', () => {
20 - function normalizeCodeLocInfo(str) {
21 - return str && str.replace(/at .+?:\d+/g, 'at **');
22 - }
23 -
20 let Component;
21 let RequiredPropComponent;
22
@@ -48,23 +44,16 @@ describe('ReactJSXElementValidator', () => {
44 });
45
46 it('warns for keys for arrays of elements in children position', () => {
51 - spyOnDev(console, 'error');
52 -
53 - ReactTestUtils.renderIntoDocument(
54 - <Component>{[<Component />, <Component />]}</Component>,
47 + expect(() =>
48 + ReactTestUtils.renderIntoDocument(
49 + <Component>{[<Component />, <Component />]}</Component>,
50 + ),
51 + ).toWarnDev(
52 + 'Each child in an array or iterator should have a unique "key" prop.',
53 );
56 -
57 - if (__DEV__) {
58 - expect(console.error.calls.count()).toBe(1);
59 - expect(console.error.calls.argsFor(0)[0]).toContain(
60 - 'Each child in an array or iterator should have a unique "key" prop.',
61 - );
62 - }
54 });
55
56 it('warns for keys for arrays of elements with owner info', () => {
66 - spyOnDev(console, 'error');
67 -
57 class InnerComponent extends React.Component {
58 render() {
59 return <Component>{this.props.childSet}</Component>;
@@ -77,21 +66,16 @@ describe('ReactJSXElementValidator', () => {
66 }
67 }
68
80 - ReactTestUtils.renderIntoDocument(<ComponentWrapper />);
81 -
82 - if (__DEV__) {
83 - expect(console.error.calls.count()).toBe(1);
84 - expect(console.error.calls.argsFor(0)[0]).toContain(
85 - 'Each child in an array or iterator should have a unique "key" prop.' +
86 - '\n\nCheck the render method of `InnerComponent`. ' +
87 - 'It was passed a child from ComponentWrapper. ',
88 - );
89 - }
69 + expect(() =>
70 + ReactTestUtils.renderIntoDocument(<ComponentWrapper />),
71 + ).toWarnDev(
72 + 'Each child in an array or iterator should have a unique "key" prop.' +
73 + '\n\nCheck the render method of `InnerComponent`. ' +
74 + 'It was passed a child from ComponentWrapper. ',
75 + );
76 });
77
78 it('warns for keys for iterables of elements in rest args', () => {
93 - spyOnDev(console, 'error');
94 -
79 const iterable = {
80 '@@iterator': function() {
81 let i = 0;
@@ -104,14 +88,11 @@ describe('ReactJSXElementValidator', () => {
88 },
89 };
90
107 - ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
108 -
109 - if (__DEV__) {
110 - expect(console.error.calls.count()).toBe(1);
111 - expect(console.error.calls.argsFor(0)[0]).toContain(
112 - 'Each child in an array or iterator should have a unique "key" prop.',
113 - );
114 - }
91 + expect(() =>
92 + ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>),
93 + ).toWarnDev(
94 + 'Each child in an array or iterator should have a unique "key" prop.',
95 + );
96 });
97
98 it('does not warn for arrays of elements with keys', () => {
@@ -173,7 +154,6 @@ describe('ReactJSXElementValidator', () => {
154 // In this test, we're making sure that if a proptype error is found in a
155 // component, we give a small hint as to which parent instantiated that
156 // component as per warnings about key usage in ReactElementValidator.
176 - spyOnDev(console, 'error');
157 class MyComp extends React.Component {
158 render() {
159 return <div>My color is {this.color}</div>;
@@ -187,20 +167,16 @@ describe('ReactJSXElementValidator', () => {
167 return <MyComp color={123} />;
168 }
169 }
190 - ReactTestUtils.renderIntoDocument(<ParentComp />);
191 - if (__DEV__) {
192 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
193 - 'Warning: Failed prop type: ' +
194 - 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
195 - 'expected `string`.\n' +
196 - ' in MyComp (at **)\n' +
197 - ' in ParentComp (at **)',
198 - );
199 - }
170 + expect(() => ReactTestUtils.renderIntoDocument(<ParentComp />)).toWarnDev(
171 + 'Warning: Failed prop type: ' +
172 + 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
173 + 'expected `string`.\n' +
174 + ' in MyComp (at **)\n' +
175 + ' in ParentComp (at **)',
176 + );
177 });
178
179 it('should update component stack after receiving next element', () => {
203 - spyOnDev(console, 'error');
180 function MyComp() {
181 return null;
182 }
@@ -221,21 +197,16 @@ describe('ReactJSXElementValidator', () => {
197
198 const container = document.createElement('div');
199 ReactDOM.render(<ParentComp warn={false} />, container);
224 - ReactDOM.render(<ParentComp warn={true} />, container);
225 -
226 - if (__DEV__) {
227 - expect(console.error.calls.count()).toBe(1);
228 - // The warning should have the full stack with line numbers.
229 - // If it doesn't, it means we're using information from the old element.
230 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
231 - 'Warning: Failed prop type: ' +
232 - 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
233 - 'expected `string`.\n' +
234 - ' in MyComp (at **)\n' +
235 - ' in MiddleComp (at **)\n' +
236 - ' in ParentComp (at **)',
237 - );
238 - }
200 + expect(() =>
201 + ReactDOM.render(<ParentComp warn={true} />, container),
202 + ).toWarnDev(
203 + 'Warning: Failed prop type: ' +
204 + 'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
205 + 'expected `string`.\n' +
206 + ' in MyComp (at **)\n' +
207 + ' in MiddleComp (at **)\n' +
208 + ' in ParentComp (at **)',
209 + );
210 });
211
212 it('gives a helpful error when passing null, undefined, or boolean', () => {
@@ -243,100 +214,72 @@ describe('ReactJSXElementValidator', () => {
214 const Null = null;
215 const True = true;
216 const Div = 'div';
246 - spyOnDev(console, 'error');
247 - void <Undefined />;
248 - void <Null />;
249 - void <True />;
250 - if (__DEV__) {
251 - expect(console.error.calls.count()).toBe(3);
252 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
253 - 'Warning: React.createElement: type is invalid -- expected a string ' +
254 - '(for built-in components) or a class/function (for composite ' +
255 - 'components) but got: undefined. You likely forgot to export your ' +
256 - "component from the file it's defined in, or you might have mixed up " +
257 - 'default and named imports.' +
258 - '\n\nCheck your code at **.',
259 - );
260 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
261 - 'Warning: React.createElement: type is invalid -- expected a string ' +
262 - '(for built-in components) or a class/function (for composite ' +
263 - 'components) but got: null.' +
264 - '\n\nCheck your code at **.',
265 - );
266 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
267 - 'Warning: React.createElement: type is invalid -- expected a string ' +
268 - '(for built-in components) or a class/function (for composite ' +
269 - 'components) but got: boolean.' +
270 - '\n\nCheck your code at **.',
271 - );
272 - }
217 + expect(() => void <Undefined />).toWarnDev(
218 + 'Warning: React.createElement: type is invalid -- expected a string ' +
219 + '(for built-in components) or a class/function (for composite ' +
220 + 'components) but got: undefined. You likely forgot to export your ' +
221 + "component from the file it's defined in, or you might have mixed up " +
222 + 'default and named imports.' +
223 + '\n\nCheck your code at **.',
224 + );
225 + expect(() => void <Null />).toWarnDev(
226 + 'Warning: React.createElement: type is invalid -- expected a string ' +
227 + '(for built-in components) or a class/function (for composite ' +
228 + 'components) but got: null.' +
229 + '\n\nCheck your code at **.',
230 + );
231 + expect(() => void <True />).toWarnDev(
232 + 'Warning: React.createElement: type is invalid -- expected a string ' +
233 + '(for built-in components) or a class/function (for composite ' +
234 + 'components) but got: boolean.' +
235 + '\n\nCheck your code at **.',
236 + );
237 + // No error expected
238 void <Div />;
274 - if (__DEV__) {
275 - expect(console.error.calls.count()).toBe(3);
276 - }
239 });
240
241 it('should check default prop values', () => {
280 - spyOnDev(console, 'error');
281 -
242 RequiredPropComponent.defaultProps = {prop: null};
243
284 - ReactTestUtils.renderIntoDocument(<RequiredPropComponent />);
285 -
286 - if (__DEV__) {
287 - expect(console.error.calls.count()).toBe(1);
288 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
289 - 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
290 - '`RequiredPropComponent`, but its value is `null`.\n' +
291 - ' in RequiredPropComponent (at **)',
292 - );
293 - }
244 + expect(() =>
245 + ReactTestUtils.renderIntoDocument(<RequiredPropComponent />),
246 + ).toWarnDev(
247 + 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
248 + '`RequiredPropComponent`, but its value is `null`.\n' +
249 + ' in RequiredPropComponent (at **)',
250 + );
251 });
252
253 it('should not check the default for explicit null', () => {
297 - spyOnDev(console, 'error');
298 -
299 - ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={null} />);
300 -
301 - if (__DEV__) {
302 - expect(console.error.calls.count()).toBe(1);
303 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
304 - 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
305 - '`RequiredPropComponent`, but its value is `null`.\n' +
306 - ' in RequiredPropComponent (at **)',
307 - );
308 - }
254 + expect(() =>
255 + ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={null} />),
256 + ).toWarnDev(
257 + 'Warning: Failed prop type: The prop `prop` is marked as required in ' +
258 + '`RequiredPropComponent`, but its value is `null`.\n' +
259 + ' in RequiredPropComponent (at **)',
260 + );
261 });
262
263 it('should check declared prop types', () => {
312 - spyOnDev(console, 'error');
313 -
314 - ReactTestUtils.renderIntoDocument(<RequiredPropComponent />);
315 - ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={42} />);
316 -
317 - if (__DEV__) {
318 - expect(console.error.calls.count()).toBe(2);
319 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
320 - 'Warning: Failed prop type: ' +
321 - 'The prop `prop` is marked as required in `RequiredPropComponent`, but ' +
322 - 'its value is `undefined`.\n' +
323 - ' in RequiredPropComponent (at **)',
324 - );
325 -
326 - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
327 - 'Warning: Failed prop type: ' +
328 - 'Invalid prop `prop` of type `number` supplied to ' +
329 - '`RequiredPropComponent`, expected `string`.\n' +
330 - ' in RequiredPropComponent (at **)',
331 - );
332 - }
264 + expect(() =>
265 + ReactTestUtils.renderIntoDocument(<RequiredPropComponent />),
266 + ).toWarnDev(
267 + 'Warning: Failed prop type: ' +
268 + 'The prop `prop` is marked as required in `RequiredPropComponent`, but ' +
269 + 'its value is `undefined`.\n' +
270 + ' in RequiredPropComponent (at **)',
271 + );
272 + expect(() =>
273 + ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={42} />),
274 + ).toWarnDev(
275 + 'Warning: Failed prop type: ' +
276 + 'Invalid prop `prop` of type `number` supplied to ' +
277 + '`RequiredPropComponent`, expected `string`.\n' +
278 + ' in RequiredPropComponent (at **)',
279 + );
280
281 + // Should not error for strings
282 ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop="string" />);
335 -
336 - if (__DEV__) {
337 - // Should not error for strings
338 - expect(console.error.calls.count()).toBe(2);
339 - }
283 });
284
285 it('should warn on invalid prop types', () => {
@@ -344,7 +287,6 @@ describe('ReactJSXElementValidator', () => {
287 // for us to issue a warning earlier than element creation when the error
288 // actually occurs. Since this step is skipped in production, we should just
289 // warn instead of throwing for this case.
347 - spyOnDev(console, 'error');
290 class NullPropTypeComponent extends React.Component {
291 render() {
292 return <span>{this.props.prop}</span>;
@@ -353,18 +295,15 @@ describe('ReactJSXElementValidator', () => {
295 NullPropTypeComponent.propTypes = {
296 prop: null,
297 };
356 - ReactTestUtils.renderIntoDocument(<NullPropTypeComponent />);
357 - if (__DEV__) {
358 - expect(console.error.calls.count()).toBe(1);
359 - expect(console.error.calls.argsFor(0)[0]).toContain(
360 - 'NullPropTypeComponent: prop type `prop` is invalid; it must be a ' +
361 - 'function, usually from the `prop-types` package,',
362 - );
363 - }
298 + expect(() =>
299 + ReactTestUtils.renderIntoDocument(<NullPropTypeComponent />),
300 + ).toWarnDev(
301 + 'NullPropTypeComponent: prop type `prop` is invalid; it must be a ' +
302 + 'function, usually from the `prop-types` package,',
303 + );
304 });
305
306 it('should warn on invalid context types', () => {
367 - spyOnDev(console, 'error');
307 class NullContextTypeComponent extends React.Component {
308 render() {
309 return <span>{this.props.prop}</span>;
@@ -373,18 +312,15 @@ describe('ReactJSXElementValidator', () => {
312 NullContextTypeComponent.contextTypes = {
313 prop: null,
314 };
376 - ReactTestUtils.renderIntoDocument(<NullContextTypeComponent />);
377 - if (__DEV__) {
378 - expect(console.error.calls.count()).toBe(1);
379 - expect(console.error.calls.argsFor(0)[0]).toContain(
380 - 'NullContextTypeComponent: context type `prop` is invalid; it must ' +
381 - 'be a function, usually from the `prop-types` package,',
382 - );
383 - }
315 + expect(() =>
316 + ReactTestUtils.renderIntoDocument(<NullContextTypeComponent />),
317 + ).toWarnDev(
318 + 'NullContextTypeComponent: context type `prop` is invalid; it must ' +
319 + 'be a function, usually from the `prop-types` package,',
320 + );
321 });
322
323 it('should warn if getDefaultProps is specificed on the class', () => {
387 - spyOnDev(console, 'error');
324 class GetDefaultPropsComponent extends React.Component {
325 render() {
326 return <span>{this.props.prop}</span>;
@@ -393,18 +329,15 @@ describe('ReactJSXElementValidator', () => {
329 GetDefaultPropsComponent.getDefaultProps = () => ({
330 prop: 'foo',
331 });
396 - ReactTestUtils.renderIntoDocument(<GetDefaultPropsComponent />);
397 - if (__DEV__) {
398 - expect(console.error.calls.count()).toBe(1);
399 - expect(console.error.calls.argsFor(0)[0]).toContain(
400 - 'getDefaultProps is only used on classic React.createClass definitions.' +
401 - ' Use a static property named `defaultProps` instead.',
402 - );
403 - }
332 + expect(() =>
333 + ReactTestUtils.renderIntoDocument(<GetDefaultPropsComponent />),
334 + ).toWarnDev(
335 + 'getDefaultProps is only used on classic React.createClass definitions.' +
336 + ' Use a static property named `defaultProps` instead.',
337 + );
338 });
339
340 it('should warn if component declares PropTypes instead of propTypes', () => {
407 - spyOnDevAndProd(console, 'error');
341 class MisspelledPropTypesComponent extends React.Component {
342 render() {
343 return <span>{this.props.prop}</span>;
@@ -413,46 +346,30 @@ describe('ReactJSXElementValidator', () => {
346 MisspelledPropTypesComponent.PropTypes = {
347 prop: PropTypes.string,
348 };
416 - ReactTestUtils.renderIntoDocument(
417 - <MisspelledPropTypesComponent prop="hi" />,
349 + expect(() =>
350 + ReactTestUtils.renderIntoDocument(
351 + <MisspelledPropTypesComponent prop="hi" />,
352 + ),
353 + ).toWarnDev(
354 + 'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
355 + 'instead of `propTypes`. Did you misspell the property assignment?',
356 );
419 - if (__DEV__) {
420 - expect(console.error.calls.count()).toBe(1);
421 - expect(console.error.calls.argsFor(0)[0]).toBe(
422 - 'Warning: Component MisspelledPropTypesComponent declared `PropTypes` ' +
423 - 'instead of `propTypes`. Did you misspell the property assignment?',
424 - );
425 - }
357 });
358
359 it('warns for fragments with illegal attributes', () => {
429 - spyOnDev(console, 'error');
430 -
360 class Foo extends React.Component {
361 render() {
433 - return (
434 - <React.Fragment a={1} b={2}>
435 - hello
436 - </React.Fragment>
437 - );
362 + return <React.Fragment a={1}>hello</React.Fragment>;
363 }
364 }
365
441 - ReactTestUtils.renderIntoDocument(<Foo />);
442 -
443 - if (__DEV__) {
444 - expect(console.error.calls.count()).toBe(1);
445 - expect(console.error.calls.argsFor(0)[0]).toContain('Invalid prop `');
446 - expect(console.error.calls.argsFor(0)[0]).toContain(
447 - '` supplied to `React.Fragment`. React.Fragment ' +
448 - 'can only have `key` and `children` props.',
449 - );
450 - }
366 + expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toWarnDev(
367 + 'Invalid prop `a` supplied to `React.Fragment`. React.Fragment ' +
368 + 'can only have `key` and `children` props.',
369 + );
370 });
371
372 it('warns for fragments with refs', () => {
454 - spyOnDev(console, 'error');
455 -
373 class Foo extends React.Component {
374 render() {
375 return (
@@ -466,14 +383,9 @@ describe('ReactJSXElementValidator', () => {
383 }
384 }
385
469 - ReactTestUtils.renderIntoDocument(<Foo />);
470 -
471 - if (__DEV__) {
472 - expect(console.error.calls.count()).toBe(1);
473 - expect(console.error.calls.argsFor(0)[0]).toContain(
474 - 'Invalid attribute `ref` supplied to `React.Fragment`.',
475 - );
476 - }
386 + expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toWarnDev(
387 + 'Invalid attribute `ref` supplied to `React.Fragment`.',
388 + );
389 });
390
391 it('does not warn for fragments of multiple elements without keys', () => {
@@ -486,21 +398,14 @@ describe('ReactJSXElementValidator', () => {
398 });
399
400 it('warns for fragments of multiple elements with same key', () => {
489 - spyOnDev(console, 'error');
490 -
491 - ReactTestUtils.renderIntoDocument(
492 - <React.Fragment>
493 - <span key="a">1</span>
494 - <span key="a">2</span>
495 - <span key="b">3</span>
496 - </React.Fragment>,
497 - );
498 -
499 - if (__DEV__) {
500 - expect(console.error.calls.count()).toBe(1);
501 - expect(console.error.calls.argsFor(0)[0]).toContain(
502 - 'Encountered two children with the same key, `a`.',
503 - );
504 - }
401 + expect(() =>
402 + ReactTestUtils.renderIntoDocument(
403 + <React.Fragment>
404 + <span key="a">1</span>
405 + <span key="a">2</span>
406 + <span key="b">3</span>
407 + </React.Fragment>,
408 + ),
409 + ).toWarnDev('Encountered two children with the same key, `a`.');
410 });
411 });
packages/react/src/__tests__/ReactPureComponent-test.js
+13 -24
@@ -62,7 +62,6 @@ describe('ReactPureComponent', () => {
62 });
63
64 it('can override shouldComponentUpdate', () => {
65 - spyOnDev(console, 'error');
65 let renders = 0;
66 class Component extends React.PureComponent {
67 render() {
@@ -73,18 +72,15 @@ describe('ReactPureComponent', () => {
72 return true;
73 }
74 }
75 +
76 const container = document.createElement('div');
77 + expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
78 + 'Warning: ' +
79 + 'Component has a method called shouldComponentUpdate(). ' +
80 + 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
81 + 'Please extend React.Component if shouldComponentUpdate is used.',
82 + );
83 ReactDOM.render(<Component />, container);
78 - ReactDOM.render(<Component />, container);
79 - if (__DEV__) {
80 - expect(console.error.calls.count()).toBe(1);
81 - expect(console.error.calls.argsFor(0)[0]).toBe(
82 - 'Warning: ' +
83 - 'Component has a method called shouldComponentUpdate(). ' +
84 - 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
85 - 'Please extend React.Component if shouldComponentUpdate is used.',
86 - );
87 - }
84 expect(renders).toBe(2);
85 });
86
@@ -103,8 +99,6 @@ describe('ReactPureComponent', () => {
99 });
100
101 it('should warn when shouldComponentUpdate is defined on React.PureComponent', () => {
106 - spyOnDev(console, 'error');
107 -
102 class PureComponent extends React.PureComponent {
103 shouldComponentUpdate() {
104 return true;
@@ -114,16 +108,11 @@ describe('ReactPureComponent', () => {
108 }
109 }
110 const container = document.createElement('div');
117 - ReactDOM.render(<PureComponent />, container);
118 -
119 - if (__DEV__) {
120 - expect(console.error.calls.count()).toBe(1);
121 - expect(console.error.calls.argsFor(0)[0]).toBe(
122 - 'Warning: ' +
123 - 'PureComponent has a method called shouldComponentUpdate(). ' +
124 - 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
125 - 'Please extend React.Component if shouldComponentUpdate is used.',
126 - );
127 - }
111 + expect(() => ReactDOM.render(<PureComponent />, container)).toWarnDev(
112 + 'Warning: ' +
113 + 'PureComponent has a method called shouldComponentUpdate(). ' +
114 + 'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
115 + 'Please extend React.Component if shouldComponentUpdate is used.',
116 + );
117 });
118 });
packages/react/src/__tests__/ReactTypeScriptClass-test.ts
+117 -159
@@ -26,7 +26,7 @@ class Inner extends React.Component {
26 render() {
27 attachedListener = this.props.onClick;
28 renderedName = this.props.name;
29 - return React.createElement('div', { className: this.props.name });
29 + return React.createElement('div', {className: this.props.name});
30 }
31 }
32
@@ -45,11 +45,11 @@ function test(element, expectedTag, expectedClassName) {
45
46 // it preserves the name of the class for use in error messages
47 // it throws if no render function is defined
48 -class Empty extends React.Component { }
48 +class Empty extends React.Component {}
49
50 // it renders a simple stateless component with prop
51 class SimpleStateless extends React.Component {
52 - props : any;
52 + props: any;
53 render() {
54 return React.createElement(Inner, {name: this.props.bar});
55 }
@@ -58,7 +58,7 @@ class SimpleStateless extends React.Component {
58 // it renders based on state using initial values in this.props
59 class InitialState extends React.Component {
60 state = {
61 - bar: this.props.initialValue
61 + bar: this.props.initialValue,
62 };
63 render() {
64 return React.createElement('span', {className: this.state.bar});
@@ -69,10 +69,10 @@ class InitialState extends React.Component {
69 class StateBasedOnProps extends React.Component {
70 constructor(props) {
71 super(props);
72 - this.state = { bar: props.initialValue };
72 + this.state = {bar: props.initialValue};
73 }
74 changeState() {
75 - this.setState({ bar: 'bar' });
75 + this.setState({bar: 'bar'});
76 }
77 render() {
78 if (this.state.bar === 'foo') {
@@ -86,11 +86,11 @@ class StateBasedOnProps extends React.Component {
86 class StateBasedOnContext extends React.Component {
87 static contextTypes = {
88 tag: PropTypes.string,
89 - className: PropTypes.string
89 + className: PropTypes.string,
90 };
91 state = {
92 tag: this.context.tag,
93 - className: this.context.className
93 + className: this.context.className,
94 };
95 render() {
96 const Tag = this.state.tag;
@@ -101,10 +101,10 @@ class StateBasedOnContext extends React.Component {
101 class ProvideChildContextTypes extends React.Component {
102 static childContextTypes = {
103 tag: PropTypes.string,
104 - className: PropTypes.string
104 + className: PropTypes.string,
105 };
106 getChildContext() {
107 - return { tag: 'span', className: 'foo' };
107 + return {tag: 'span', className: 'foo'};
108 }
109 render() {
110 return React.createElement(StateBasedOnContext);
@@ -115,10 +115,10 @@ class ProvideChildContextTypes extends React.Component {
115 let renderCount = 0;
116 class RenderOnce extends React.Component {
117 state = {
118 - bar: this.props.initialValue
118 + bar: this.props.initialValue,
119 };
120 componentWillMount() {
121 - this.setState({ bar: 'bar' });
121 + this.setState({bar: 'bar'});
122 }
123 render() {
124 renderCount++;
@@ -157,57 +157,54 @@ class NullState extends React.Component {
157 // it setState through an event handler
158 class BoundEventHandler extends React.Component {
159 state = {
160 - bar: this.props.initialValue
160 + bar: this.props.initialValue,
161 };
162 handleClick = () => {
163 - this.setState({ bar: 'bar' });
163 + this.setState({bar: 'bar'});
164 };
165 render() {
166 - return (
167 - React.createElement(Inner, {
168 - name: this.state.bar,
169 - onClick: this.handleClick
170 - })
171 - );
166 + return React.createElement(Inner, {
167 + name: this.state.bar,
168 + onClick: this.handleClick,
169 + });
170 }
171 }
172
173 // it should not implicitly bind event handlers
174 class UnboundEventHandler extends React.Component {
175 state = {
178 - bar: this.props.initialValue
176 + bar: this.props.initialValue,
177 };
178 handleClick() {
181 - this.setState({ bar: 'bar' });
179 + this.setState({bar: 'bar'});
180 }
181 render() {
184 - return React.createElement(
185 - Inner, { name: this.state.bar, onClick: this.handleClick }
186 - );
182 + return React.createElement(Inner, {
183 + name: this.state.bar,
184 + onClick: this.handleClick,
185 + });
186 }
187 }
188
189 // it renders using forceUpdate even when there is no state
190 class ForceUpdateWithNoState extends React.Component {
192 - mutativeValue : string = this.props.initialValue;
191 + mutativeValue: string = this.props.initialValue;
192 handleClick() {
193 this.mutativeValue = 'bar';
194 this.forceUpdate();
195 }
196 render() {
198 - return (
199 - React.createElement(Inner, {
200 - name: this.mutativeValue,
201 - onClick: this.handleClick.bind(this)}
202 - )
203 - );
197 + return React.createElement(Inner, {
198 + name: this.mutativeValue,
199 + onClick: this.handleClick.bind(this),
200 + });
201 }
202 }
203
204 // it will call all the normal life cycle methods
205 let lifeCycles = [];
206 class NormalLifeCycles extends React.Component {
210 - props : any;
207 + props: any;
208 state = {};
209 componentWillMount() {
210 lifeCycles.push('will-mount');
@@ -278,15 +275,15 @@ class MisspelledComponent2 extends React.Component {
275
276 // it supports this.context passed via getChildContext
277 class ReadContext extends React.Component {
281 - static contextTypes = { bar: PropTypes.string };
278 + static contextTypes = {bar: PropTypes.string};
279 render() {
283 - return React.createElement('div', { className: this.context.bar });
280 + return React.createElement('div', {className: this.context.bar});
281 }
282 }
283 class ProvideContext extends React.Component {
287 - static childContextTypes = { bar: PropTypes.string };
284 + static childContextTypes = {bar: PropTypes.string};
285 getChildContext() {
289 - return { bar: 'bar-through-context' };
286 + return {bar: 'bar-through-context'};
287 }
288 render() {
289 return React.createElement(ReadContext);
@@ -303,7 +300,6 @@ class ClassicRefs extends React.Component {
300 // Describe the actual test cases.
301
302 describe('ReactTypeScriptClass', function() {
306 -
303 beforeEach(function() {
304 container = document.createElement('div');
305 attachedListener = null;
@@ -315,17 +311,14 @@ describe('ReactTypeScriptClass', function() {
311 });
312
313 it('throws if no render function is defined', function() {
318 - spyOnDev(console, 'error');
319 -
320 - expect(() => ReactDOM.render(React.createElement(Empty), container)).toThrow();
321 -
322 - if (__DEV__) {
323 - expect((<any>console.error).calls.count()).toBe(1);
324 - expect((<any>console.error).calls.argsFor(0)[0]).toBe(
325 - 'Warning: Empty(...): No `render` method found on the returned ' +
314 + expect(() =>
315 + expect(() =>
316 + ReactDOM.render(React.createElement(Empty), container)
317 + ).toThrow()
318 + ).toWarnDev(
319 + 'Warning: Empty(...): No `render` method found on the returned ' +
320 'component instance: you may have forgotten to define `render`.'
327 - );
328 - }
321 + );
322 });
323
324 it('renders a simple stateless component with prop', function() {
@@ -362,31 +355,15 @@ describe('ReactTypeScriptClass', function() {
355 });
356
357 it('should warn with non-object in the initial state property', function() {
365 - spyOnDev(console, 'error');
366 - test(React.createElement(ArrayState), 'SPAN', '');
367 - if (__DEV__) {
368 - expect((<any>console.error).calls.count()).toBe(1);
369 - expect((<any>console.error).calls.argsFor(0)[0]).toContain(
370 - 'ArrayState.state: must be set to an object or null'
371 - );
372 - (<any>console.error).calls.reset()
373 - }
374 - test(React.createElement(StringState), 'SPAN', '');
375 - if (__DEV__) {
376 - expect((<any>console.error).calls.count()).toBe(1);
377 - expect((<any>console.error).calls.argsFor(0)[0]).toContain(
378 - 'StringState.state: must be set to an object or null'
379 - );
380 - (<any>console.error).calls.reset()
381 - }
382 - test(React.createElement(NumberState), 'SPAN', '');
383 - if (__DEV__) {
384 - expect((<any>console.error).calls.count()).toBe(1);
385 - expect((<any>console.error).calls.argsFor(0)[0]).toContain(
386 - 'NumberState.state: must be set to an object or null'
387 - );
388 - (<any>console.error).calls.reset()
389 - }
358 + expect(() => test(React.createElement(ArrayState), 'SPAN', '')).toWarnDev(
359 + 'ArrayState.state: must be set to an object or null'
360 + );
361 + expect(() => test(React.createElement(StringState), 'SPAN', '')).toWarnDev(
362 + 'StringState.state: must be set to an object or null'
363 + );
364 + expect(() => test(React.createElement(NumberState), 'SPAN', '')).toWarnDev(
365 + 'NumberState.state: must be set to an object or null'
366 + );
367 });
368
369 it('should render with null in the initial state property', function() {
@@ -425,121 +402,103 @@ describe('ReactTypeScriptClass', function() {
402 it('will call all the normal life cycle methods', function() {
403 lifeCycles = [];
404 test(React.createElement(NormalLifeCycles, {value: 'foo'}), 'SPAN', 'foo');
428 - expect(lifeCycles).toEqual([
429 - 'will-mount',
430 - 'did-mount'
431 - ]);
405 + expect(lifeCycles).toEqual(['will-mount', 'did-mount']);
406 lifeCycles = []; // reset
407 test(React.createElement(NormalLifeCycles, {value: 'bar'}), 'SPAN', 'bar');
408 expect(lifeCycles).toEqual([
435 - 'receive-props', { value: 'bar' },
436 - 'should-update', { value: 'bar' }, {},
437 - 'will-update', { value: 'bar' }, {},
438 - 'did-update', { value: 'foo' }, {}
409 + 'receive-props',
410 + {value: 'bar'},
411 + 'should-update',
412 + {value: 'bar'},
413 + {},
414 + 'will-update',
415 + {value: 'bar'},
416 + {},
417 + 'did-update',
418 + {value: 'foo'},
419 + {},
420 ]);
421 lifeCycles = []; // reset
422 ReactDOM.unmountComponentAtNode(container);
442 - expect(lifeCycles).toEqual([
443 - 'will-unmount'
444 - ]);
423 + expect(lifeCycles).toEqual(['will-unmount']);
424 });
425
447 - it('warns when classic properties are defined on the instance, ' +
448 - 'but does not invoke them.', function() {
449 - spyOnDev(console, 'error');
450 - getInitialStateWasCalled = false;
451 - getDefaultPropsWasCalled = false;
452 - test(React.createElement(ClassicProperties), 'SPAN', 'foo');
453 - expect(getInitialStateWasCalled).toBe(false);
454 - expect(getDefaultPropsWasCalled).toBe(false);
455 -
456 - if (__DEV__) {
457 - expect((<any>console.error).calls.count()).toBe(4);
458 - expect((<any>console.error).calls.argsFor(0)[0]).toContain(
426 + it(
427 + 'warns when classic properties are defined on the instance, ' +
428 + 'but does not invoke them.',
429 + function() {
430 + getInitialStateWasCalled = false;
431 + getDefaultPropsWasCalled = false;
432 + expect(() =>
433 + test(React.createElement(ClassicProperties), 'SPAN', 'foo')
434 + ).toWarnDev([
435 'getInitialState was defined on ClassicProperties, ' +
460 - 'a plain JavaScript class.'
461 - );
462 - expect((<any>console.error).calls.argsFor(1)[0]).toContain(
436 + 'a plain JavaScript class.',
437 'getDefaultProps was defined on ClassicProperties, ' +
464 - 'a plain JavaScript class.'
465 - );
466 - expect((<any>console.error).calls.argsFor(2)[0]).toContain(
467 - 'propTypes was defined as an instance property on ClassicProperties.'
468 - );
469 - expect((<any>console.error).calls.argsFor(3)[0]).toContain(
470 - 'contextTypes was defined as an instance property on ClassicProperties.'
471 - );
438 + 'a plain JavaScript class.',
439 + 'propTypes was defined as an instance property on ClassicProperties.',
440 + 'contextTypes was defined as an instance property on ClassicProperties.',
441 + ]);
442 + expect(getInitialStateWasCalled).toBe(false);
443 + expect(getDefaultPropsWasCalled).toBe(false);
444 }
473 - });
474 -
475 - it('does not warn about getInitialState() on class components ' +
476 - 'if state is also defined.', () => {
477 - spyOnDev(console, 'error');
478 -
479 - class Example extends React.Component {
480 - state = {};
481 - getInitialState() {
482 - return {};
483 - }
484 - render() {
485 - return React.createElement('span', {className: 'foo'});
445 + );
446 +
447 + it(
448 + 'does not warn about getInitialState() on class components ' +
449 + 'if state is also defined.',
450 + () => {
451 + class Example extends React.Component {
452 + state = {};
453 + getInitialState() {
454 + return {};
455 + }
456 + render() {
457 + return React.createElement('span', {className: 'foo'});
458 + }
459 }
487 - }
460
489 - test(React.createElement(Example), 'SPAN', 'foo');
490 - if (__DEV__) {
491 - expect((<any>console.error).calls.count()).toBe(0);
461 + test(React.createElement(Example), 'SPAN', 'foo');
462 }
493 - });
463 + );
464
465 it('should warn when misspelling shouldComponentUpdate', function() {
496 - spyOnDev(console, 'error');
497 -
498 - test(React.createElement(MisspelledComponent1), 'SPAN', 'foo');
499 -
500 - if (__DEV__) {
501 - expect((<any>console.error).calls.count()).toBe(1);
502 - expect((<any>console.error).calls.argsFor(0)[0]).toBe(
503 - 'Warning: ' +
466 + expect(() =>
467 + test(React.createElement(MisspelledComponent1), 'SPAN', 'foo')
468 + ).toWarnDev(
469 + 'Warning: ' +
470 'MisspelledComponent1 has a method called componentShouldUpdate(). Did ' +
471 'you mean shouldComponentUpdate()? The name is phrased as a question ' +
472 'because the function is expected to return a value.'
507 - );
508 - }
473 + );
474 });
475
476 it('should warn when misspelling componentWillReceiveProps', function() {
512 - spyOnDev(console, 'error');
513 -
514 - test(React.createElement(MisspelledComponent2), 'SPAN', 'foo');
515 -
516 - if (__DEV__) {
517 - expect((<any>console.error).calls.count()).toBe(1);
518 - expect((<any>console.error).calls.argsFor(0)[0]).toBe(
519 - 'Warning: ' +
477 + expect(() =>
478 + test(React.createElement(MisspelledComponent2), 'SPAN', 'foo')
479 + ).toWarnDev(
480 + 'Warning: ' +
481 'MisspelledComponent2 has a method called componentWillRecieveProps(). ' +
482 'Did you mean componentWillReceiveProps()?'
522 - );
523 - }
483 + );
484 });
485
486 it('should throw AND warn when trying to access classic APIs', function() {
527 - spyOnDev(console, 'warn');
487 const instance = test(
488 React.createElement(Inner, {name: 'foo'}),
530 - 'DIV','foo'
489 + 'DIV',
490 + 'foo'
491 + );
492 + expect(() =>
493 + expect(() => instance.replaceState({})).toThrow()
494 + ).toLowPriorityWarnDev(
495 + 'replaceState(...) is deprecated in plain JavaScript React classes'
496 + );
497 + expect(() =>
498 + expect(() => instance.isMounted()).toThrow()
499 + ).toLowPriorityWarnDev(
500 + 'isMounted(...) is deprecated in plain JavaScript React classes'
501 );
532 - expect(() => instance.replaceState({})).toThrow();
533 - expect(() => instance.isMounted()).toThrow();
534 - if (__DEV__) {
535 - expect((<any>console.warn).calls.count()).toBe(2);
536 - expect((<any>console.warn).calls.argsFor(0)[0]).toContain(
537 - 'replaceState(...) is deprecated in plain JavaScript React classes'
538 - );
539 - expect((<any>console.warn).calls.argsFor(1)[0]).toContain(
540 - 'isMounted(...) is deprecated in plain JavaScript React classes'
541 - );
542 - }
502 });
503
504 it('supports this.context passed via getChildContext', function() {
@@ -560,5 +519,4 @@ describe('ReactTypeScriptClass', function() {
519 const node = ReactDOM.findDOMNode(instance);
520 expect(node).toBe(container.firstChild);
521 });
563 -
522 });
packages/react/src/__tests__/createReactClassIntegration-test.js
+121 -158
@@ -52,121 +52,103 @@ describe('create-react-class-integration', () => {
52 });
53
54 it('should warn on invalid prop types', () => {
55 - spyOnDev(console, 'error');
56 - createReactClass({
57 - displayName: 'Component',
58 - propTypes: {
59 - prop: null,
60 - },
61 - render: function() {
62 - return <span>{this.props.prop}</span>;
63 - },
64 - });
65 - if (__DEV__) {
66 - expect(console.error.calls.count()).toBe(1);
67 - expect(console.error.calls.argsFor(0)[0]).toBe(
68 - 'Warning: Component: prop type `prop` is invalid; ' +
69 - 'it must be a function, usually from React.PropTypes.',
70 - );
71 - }
55 + expect(() =>
56 + createReactClass({
57 + displayName: 'Component',
58 + propTypes: {
59 + prop: null,
60 + },
61 + render: function() {
62 + return <span>{this.props.prop}</span>;
63 + },
64 + }),
65 + ).toWarnDev(
66 + 'Warning: Component: prop type `prop` is invalid; ' +
67 + 'it must be a function, usually from React.PropTypes.',
68 + );
69 });
70
71 it('should warn on invalid context types', () => {
75 - spyOnDev(console, 'error');
76 - createReactClass({
77 - displayName: 'Component',
78 - contextTypes: {
79 - prop: null,
80 - },
81 - render: function() {
82 - return <span>{this.props.prop}</span>;
83 - },
84 - });
85 - if (__DEV__) {
86 - expect(console.error.calls.count()).toBe(1);
87 - expect(console.error.calls.argsFor(0)[0]).toBe(
88 - 'Warning: Component: context type `prop` is invalid; ' +
89 - 'it must be a function, usually from React.PropTypes.',
90 - );
91 - }
72 + expect(() =>
73 + createReactClass({
74 + displayName: 'Component',
75 + contextTypes: {
76 + prop: null,
77 + },
78 + render: function() {
79 + return <span>{this.props.prop}</span>;
80 + },
81 + }),
82 + ).toWarnDev(
83 + 'Warning: Component: context type `prop` is invalid; ' +
84 + 'it must be a function, usually from React.PropTypes.',
85 + );
86 });
87
88 it('should throw on invalid child context types', () => {
95 - spyOnDev(console, 'error');
96 - createReactClass({
97 - displayName: 'Component',
98 - childContextTypes: {
99 - prop: null,
100 - },
101 - render: function() {
102 - return <span>{this.props.prop}</span>;
103 - },
104 - });
105 - if (__DEV__) {
106 - expect(console.error.calls.count()).toBe(1);
107 - expect(console.error.calls.argsFor(0)[0]).toBe(
108 - 'Warning: Component: child context type `prop` is invalid; ' +
109 - 'it must be a function, usually from React.PropTypes.',
110 - );
111 - }
89 + expect(() =>
90 + createReactClass({
91 + displayName: 'Component',
92 + childContextTypes: {
93 + prop: null,
94 + },
95 + render: function() {
96 + return <span>{this.props.prop}</span>;
97 + },
98 + }),
99 + ).toWarnDev(
100 + 'Warning: Component: child context type `prop` is invalid; ' +
101 + 'it must be a function, usually from React.PropTypes.',
102 + );
103 });
104
105 it('should warn when misspelling shouldComponentUpdate', () => {
115 - spyOnDev(console, 'error');
116 -
117 - createReactClass({
118 - componentShouldUpdate: function() {
119 - return false;
120 - },
121 - render: function() {
122 - return <div />;
123 - },
124 - });
125 - if (__DEV__) {
126 - expect(console.error.calls.count()).toBe(1);
127 - expect(console.error.calls.argsFor(0)[0]).toBe(
128 - 'Warning: A component has a method called componentShouldUpdate(). Did you ' +
129 - 'mean shouldComponentUpdate()? The name is phrased as a question ' +
130 - 'because the function is expected to return a value.',
131 - );
132 - }
106 + expect(() =>
107 + createReactClass({
108 + componentShouldUpdate: function() {
109 + return false;
110 + },
111 + render: function() {
112 + return <div />;
113 + },
114 + }),
115 + ).toWarnDev(
116 + 'Warning: A component has a method called componentShouldUpdate(). Did you ' +
117 + 'mean shouldComponentUpdate()? The name is phrased as a question ' +
118 + 'because the function is expected to return a value.',
119 + );
120
134 - createReactClass({
135 - displayName: 'NamedComponent',
136 - componentShouldUpdate: function() {
137 - return false;
138 - },
139 - render: function() {
140 - return <div />;
141 - },
142 - });
143 - if (__DEV__) {
144 - expect(console.error.calls.count()).toBe(2);
145 - expect(console.error.calls.argsFor(1)[0]).toBe(
146 - 'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' +
147 - 'mean shouldComponentUpdate()? The name is phrased as a question ' +
148 - 'because the function is expected to return a value.',
149 - );
150 - }
121 + expect(() =>
122 + createReactClass({
123 + displayName: 'NamedComponent',
124 + componentShouldUpdate: function() {
125 + return false;
126 + },
127 + render: function() {
128 + return <div />;
129 + },
130 + }),
131 + ).toWarnDev(
132 + 'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' +
133 + 'mean shouldComponentUpdate()? The name is phrased as a question ' +
134 + 'because the function is expected to return a value.',
135 + );
136 });
137
138 it('should warn when misspelling componentWillReceiveProps', () => {
154 - spyOnDev(console, 'error');
155 - createReactClass({
156 - componentWillRecieveProps: function() {
157 - return false;
158 - },
159 - render: function() {
160 - return <div />;
161 - },
162 - });
163 - if (__DEV__) {
164 - expect(console.error.calls.count()).toBe(1);
165 - expect(console.error.calls.argsFor(0)[0]).toBe(
166 - 'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
167 - 'mean componentWillReceiveProps()?',
168 - );
169 - }
139 + expect(() =>
140 + createReactClass({
141 + componentWillRecieveProps: function() {
142 + return false;
143 + },
144 + render: function() {
145 + return <div />;
146 + },
147 + }),
148 + ).toWarnDev(
149 + 'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
150 + 'mean componentWillReceiveProps()?',
151 + );
152 });
153
154 it('should throw if a reserved property is in statics', () => {
@@ -195,41 +177,32 @@ describe('create-react-class-integration', () => {
177 // TODO: Consider actually moving these to statics or drop this unit test.
178
179 xit('should warn when using deprecated non-static spec keys', () => {
198 - spyOnDev(console, 'error');
199 - createReactClass({
200 - mixins: [{}],
201 - propTypes: {
202 - foo: PropTypes.string,
203 - },
204 - contextTypes: {
205 - foo: PropTypes.string,
206 - },
207 - childContextTypes: {
208 - foo: PropTypes.string,
209 - },
210 - render: function() {
211 - return <div />;
212 - },
213 - });
214 - if (__DEV__) {
215 - expect(console.error.calls.count()).toBe(4);
216 - expect(console.error.calls.argsFor(0)[0]).toBe(
217 - 'createClass(...): `mixins` is now a static property and should ' +
218 - 'be defined inside "statics".',
219 - );
220 - expect(console.error.calls.argsFor(1)[0]).toBe(
221 - 'createClass(...): `propTypes` is now a static property and should ' +
222 - 'be defined inside "statics".',
223 - );
224 - expect(console.error.calls.argsFor(2)[0]).toBe(
225 - 'createClass(...): `contextTypes` is now a static property and ' +
226 - 'should be defined inside "statics".',
227 - );
228 - expect(console.error.calls.argsFor(3)[0]).toBe(
229 - 'createClass(...): `childContextTypes` is now a static property and ' +
230 - 'should be defined inside "statics".',
231 - );
232 - }
180 + expect(() =>
181 + createReactClass({
182 + mixins: [{}],
183 + propTypes: {
184 + foo: PropTypes.string,
185 + },
186 + contextTypes: {
187 + foo: PropTypes.string,
188 + },
189 + childContextTypes: {
190 + foo: PropTypes.string,
191 + },
192 + render: function() {
193 + return <div />;
194 + },
195 + }),
196 + ).toWarnDev([
197 + 'createClass(...): `mixins` is now a static property and should ' +
198 + 'be defined inside "statics".',
199 + 'createClass(...): `propTypes` is now a static property and should ' +
200 + 'be defined inside "statics".',
201 + 'createClass(...): `contextTypes` is now a static property and ' +
202 + 'should be defined inside "statics".',
203 + 'createClass(...): `childContextTypes` is now a static property and ' +
204 + 'should be defined inside "statics".',
205 + ]);
206 });
207
208 it('should support statics', () => {
@@ -342,21 +315,16 @@ describe('create-react-class-integration', () => {
315 });
316
317 it('should throw when using legacy factories', () => {
345 - spyOnDev(console, 'error');
318 const Component = createReactClass({
319 render() {
320 return <div />;
321 },
322 });
323
352 - expect(() => Component()).toThrow();
353 - if (__DEV__) {
354 - expect(console.error.calls.count()).toBe(1);
355 - expect(console.error.calls.argsFor(0)[0]).toBe(
356 - 'Warning: Something is calling a React component directly. Use a ' +
357 - 'factory or JSX instead. See: https://fb.me/react-legacyfactory',
358 - );
359 - }
324 + expect(() => expect(() => Component()).toThrow()).toWarnDev(
325 + 'Warning: Something is calling a React component directly. Use a ' +
326 + 'factory or JSX instead. See: https://fb.me/react-legacyfactory',
327 + );
328 });
329
330 it('replaceState and callback works', () => {
@@ -379,8 +347,6 @@ describe('create-react-class-integration', () => {
347 });
348
349 it('isMounted works', () => {
382 - spyOnDev(console, 'error');
383 -
350 const ops = [];
351 let instance;
352 const Component = createReactClass({
@@ -434,7 +400,13 @@ describe('create-react-class-integration', () => {
400 });
401
402 const container = document.createElement('div');
437 - ReactDOM.render(<Component />, container);
403 +
404 + expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
405 + 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
406 + 'clean up subscriptions and pending requests in componentWillUnmount ' +
407 + 'to prevent memory leaks.',
408 + );
409 +
410 ReactDOM.render(<Component />, container);
411 ReactDOM.unmountComponentAtNode(container);
412 instance.log('after unmount');
@@ -454,14 +426,5 @@ describe('create-react-class-integration', () => {
426 'componentWillUnmount: true',
427 'after unmount: false',
428 ]);
457 -
458 - if (__DEV__) {
459 - expect(console.error.calls.count()).toBe(1);
460 - expect(console.error.calls.argsFor(0)[0]).toEqual(
461 - 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
462 - 'clean up subscriptions and pending requests in componentWillUnmount ' +
463 - 'to prevent memory leaks.',
464 - );
465 - }
429 });
430 });
scripts/jest/matchers/toWarnDev.js new
+86
@@ -0,0 +1,86 @@
1 +'use strict';
2 +
3 +function normalizeCodeLocInfo(str) {
4 + return str && str.replace(/at .+?:\d+/g, 'at **');
5 +}
6 +
7 +const createMatcherFor = consoleMethod =>
8 + function matcher(callback, expectedMessages) {
9 + if (__DEV__) {
10 + // Warn about incorrect usage of matcher.
11 + if (typeof expectedMessages === 'string') {
12 + expectedMessages = [expectedMessages];
13 + } else if (!Array.isArray(expectedMessages)) {
14 + throw Error(
15 + `toWarnDev() requires a parameter of type string or an array of strings ` +
16 + `but was given ${typeof expectedMessages}.`
17 + );
18 + }
19 +
20 + const consoleSpy = message => {
21 + const normalizedMessage = normalizeCodeLocInfo(message);
22 +
23 + for (let index = 0; index < expectedMessages.length; index++) {
24 + const expectedMessage = expectedMessages[index];
25 + if (
26 + normalizedMessage === expectedMessage ||
27 + normalizedMessage.includes(expectedMessage)
28 + ) {
29 + expectedMessages.splice(index, 1);
30 + return;
31 + }
32 + }
33 +
34 + // Fail early for unexpected warnings to preserve the call stack.
35 + throw Error(
36 + `Unexpected warning recorded:\n ${this.utils.printReceived(
37 + message
38 + )}\n\nThe following expected warnings were not yet seen:\n ${this.utils.printExpected(
39 + expectedMessages.join('\n')
40 + )}`
41 + );
42 + };
43 +
44 + // TODO Decide whether we need to support nested toWarn* expectations.
45 + // If we don't need id, add a check here to see if this is already our spy,
46 + // And throw an error.
47 + const originalMethod = console[consoleMethod];
48 +
49 + // Avoid using Jest's built-in spy since it can't be removed.
50 + console[consoleMethod] = consoleSpy;
51 +
52 + try {
53 + callback();
54 +
55 + // Any remaining messages indicate a failed expectations.
56 + if (expectedMessages.length > 0) {
57 + return {
58 + message: () =>
59 + `Expected warning was not recorded:\n ${this.utils.printReceived(
60 + expectedMessages.join('\n')
61 + )}`,
62 + pass: false,
63 + };
64 + }
65 +
66 + return {pass: true};
67 + } catch (error) {
68 + // TODO Flag this error so Jest doesn't override its stack
69 + // See https://tinyurl.com/y9unakwb
70 + throw error;
71 + } finally {
72 + // Restore the unspied method so that unexpected errors fail tests.
73 + console[consoleMethod] = originalMethod;
74 + }
75 + } else {
76 + // Any uncaught errors or warnings should fail tests in production mode.
77 + callback();
78 +
79 + return {pass: true};
80 + }
81 + };
82 +
83 +module.exports = {
84 + toLowPriorityWarnDev: createMatcherFor('warn'),
85 + toWarnDev: createMatcherFor('error'),
86 +};
scripts/jest/setupTests.js
+43 -14
@@ -1,5 +1,7 @@
1 'use strict';
2
3 +const chalk = require('chalk');
4 +
5 if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
6 // Inside the class equivalence tester, we have a custom environment, let's
7 // require that instead.
@@ -39,6 +41,10 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
41 global.spyOnDevAndProd = spyOn;
42 }
43
44 + expect.extend({
45 + ...require('./matchers/toWarnDev'),
46 + });
47 +
48 // We have a Babel transform that inserts guards against infinite loops.
49 // If a loop runs for too many iterations, we throw an error and set this
50 // global variable. The global lets us detect an infinite loop even if
@@ -56,16 +62,22 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
62 });
63
64 ['error', 'warn'].forEach(methodName => {
59 - const oldMethod = console[methodName];
60 - const newMethod = function() {
61 - newMethod.__callCount++;
62 - oldMethod.apply(this, arguments);
65 + const unexpectedConsoleCallStacks = [];
66 + const newMethod = function(message) {
67 + // Capture the call stack now so we can warn about it later.
68 + // The call stack has helpful information for the test author.
69 + // Don't throw yet though b'c it might be accidentally caught and suppressed.
70 + const stack = new Error().stack;
71 + unexpectedConsoleCallStacks.push([
72 + stack.substr(stack.indexOf('\n') + 1),
73 + message,
74 + ]);
75 };
64 - newMethod.__callCount = 0;
76 +
77 console[methodName] = newMethod;
78
79 env.beforeEach(() => {
68 - newMethod.__callCount = 0;
80 + unexpectedConsoleCallStacks.length = 0;
81 });
82
83 env.afterEach(() => {
@@ -74,15 +86,32 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
86 `Test did not tear down console.${methodName} mock properly.`
87 );
88 }
77 - if (console[methodName].__callCount !== 0) {
78 - throw new Error(
79 - `Expected test not to call console.${methodName}(). ` +
80 - 'If the warning is expected, mock it out using ' +
81 - `spyOnDev(console, '${methodName}') or spyOnProd(console, '${
82 - methodName
83 - }'), ` +
84 - 'and test that the warning occurs.'
89 +
90 + if (unexpectedConsoleCallStacks.length > 0) {
91 + const messages = unexpectedConsoleCallStacks.map(
92 + ([stack, message]) =>
93 + `${chalk.red(message)}\n` +
94 + `${stack
95 + .split('\n')
96 + .map(line => chalk.gray(line))
97 + .join('\n')}`
98 );
99 +
100 + const message =
101 + `Expected test not to call ${chalk.bold(
102 + `console.${methodName}()`
103 + )}.\n\n` +
104 + 'If the warning is expected, test for it explicitly by:\n' +
105 + `1. Using the ${chalk.bold('.toWarnDev()')} / ${chalk.bold(
106 + '.toLowPriorityWarnDev()'
107 + )} matchers, or...\n` +
108 + `2. Mock it out using ${chalk.bold('spyOnDev')}(console, '${
109 + methodName
110 + }') or ${chalk.bold('spyOnProd')}(console, '${
111 + methodName
112 + }'), and test that the warning occurs.`;
113 +
114 + throw new Error(`${message}\n\n${messages.join('\n\n')}`);
115 }
116 });
117 });
scripts/jest/spec-equivalence-reporter/setupTests.js
+4
@@ -45,6 +45,10 @@ global.spyOnProd = function(...args) {
45 }
46 };
47
48 +expect.extend({
49 + ...require('../matchers/toWarnDev'),
50 +});
51 +
52 beforeEach(() => (numExpectations = 0));
53
54 jasmine.currentEnv_.addReporter({
scripts/jest/typescript/jest.d.ts
+2
@@ -21,6 +21,8 @@ interface Expect {
21 not: Expect
22 toThrow(message?: string): void
23 toThrowError(message?: string): void
24 + toWarnDev(message?: string | Array<string>): void
25 + toLowPriorityWarnDev(message?: string | Array<string>): void
26 toBe(value: any): void
27 toEqual(value: any): void
28 toBeFalsy(): void