Skip to content

Commit b5334a4

Browse files
authored
toWarnInDev matcher; throw on unexpected console.error (facebook#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
1 parent 22e2bf7 commit b5334a4

20 files changed

+978
-1381
lines changed

packages/react-art/src/__tests__/ReactART-test.js

Lines changed: 28 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,6 @@ describe('ReactART', () => {
336336
});
337337

338338
describe('ReactARTComponents', () => {
339-
function normalizeCodeLocInfo(str) {
340-
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
341-
}
342-
343339
it('should generate a <Shape> with props for drawing the Circle', () => {
344340
const circle = renderer.create(
345341
<Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
@@ -348,16 +344,13 @@ describe('ReactARTComponents', () => {
348344
});
349345

350346
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+
);
361354
});
362355

363356
it('should generate a <Shape> with props for drawing the Rectangle', () => {
@@ -368,21 +361,16 @@ describe('ReactARTComponents', () => {
368361
});
369362

370363
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+
]);
386374
});
387375

388376
it('should generate a <Shape> with props for drawing the Wedge', () => {
@@ -400,25 +388,16 @@ describe('ReactARTComponents', () => {
400388
});
401389

402390
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+
]);
423402
});
424403
});

packages/react-dom/src/__tests__/CSSPropertyOperations-test.js

Lines changed: 47 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ const React = require('react');
1313
const ReactDOM = require('react-dom');
1414
const ReactDOMServer = require('react-dom/server');
1515

16-
function normalizeCodeLocInfo(str) {
17-
return str && str.replace(/at .+?:\d+/g, 'at **');
18-
}
19-
2016
describe('CSSPropertyOperations', () => {
2117
it('should automatically append `px` to relevant styles', () => {
2218
const styles = {
@@ -91,17 +87,13 @@ describe('CSSPropertyOperations', () => {
9187
}
9288
}
9389

94-
spyOnDev(console, 'error');
9590
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+
);
10597
});
10698

10799
it('should warn when updating hyphenated style names', () => {
@@ -113,28 +105,21 @@ describe('CSSPropertyOperations', () => {
113105
}
114106
}
115107

116-
spyOnDev(console, 'error');
117108
const styles = {
118109
'-ms-transform': 'translate3d(0, 0, 0)',
119110
'-webkit-transform': 'translate3d(0, 0, 0)',
120111
};
121112
const root = document.createElement('div');
122113
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+
]);
138123
});
139124

140125
it('warns when miscapitalizing vendored style names', () => {
@@ -154,25 +139,19 @@ describe('CSSPropertyOperations', () => {
154139
}
155140
}
156141

157-
spyOnDev(console, 'error');
158142
const root = document.createElement('div');
159-
ReactDOM.render(<Comp />, root);
160-
if (__DEV__) {
143+
144+
expect(() => ReactDOM.render(<Comp />, root)).toWarnDev([
161145
// 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+
]);
176155
});
177156

178157
it('should warn about style having a trailing semicolon', () => {
@@ -193,24 +172,18 @@ describe('CSSPropertyOperations', () => {
193172
}
194173
}
195174

196-
spyOnDev(console, 'error');
197175
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+
]);
214187
});
215188

216189
it('should warn about style containing a NaN value', () => {
@@ -222,18 +195,13 @@ describe('CSSPropertyOperations', () => {
222195
}
223196
}
224197

225-
spyOnDev(console, 'error');
226198
const root = document.createElement('div');
227-
ReactDOM.render(<Comp />, root);
228199

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+
);
237205
});
238206

239207
it('should not warn when setting CSS custom properties', () => {
@@ -256,18 +224,13 @@ describe('CSSPropertyOperations', () => {
256224
}
257225
}
258226

259-
spyOnDev(console, 'error');
260227
const root = document.createElement('div');
261-
ReactDOM.render(<Comp />, root);
262228

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+
);
271234
});
272235

273236
it('should not add units to CSS custom properties', () => {

packages/react-dom/src/__tests__/DOMPropertyOperations-test.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,25 +151,22 @@ describe('DOMPropertyOperations', () => {
151151

152152
it('should not remove attributes for special properties', () => {
153153
const container = document.createElement('div');
154-
spyOnDev(console, 'error');
155154
ReactDOM.render(
156155
<input type="text" value="foo" onChange={function() {}} />,
157156
container,
158157
);
159158
expect(container.firstChild.getAttribute('value')).toBe('foo');
160159
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',
164167
);
165168
expect(container.firstChild.getAttribute('value')).toBe('foo');
166169
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-
}
173170
});
174171
});
175172
});

packages/react-dom/src/__tests__/EventPluginHub-test.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,17 @@ describe('EventPluginHub', () => {
2222
});
2323

2424
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.',
2832
);
29-
expect(function() {
30-
ReactTestUtils.SimulateNative.click(node);
31-
}).toThrowError(
33+
expect(() => ReactTestUtils.SimulateNative.click(node)).toThrowError(
3234
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
3335
);
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-
}
4036
});
4137

4238
it('should not prevent null listeners, at dispatch', () => {

0 commit comments

Comments
 (0)