mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Run Jest in production mode (#11616)
* Move Jest setup files to /dev/ subdirectory * Clone Jest /dev/ files into /prod/ * Move shared code into scripts/jest * Move Jest config into the scripts folder * Fix the equivalence test It fails because the config is now passed to Jest explicitly. But the test doesn't know about the config. To fix this, we just run it via `yarn test` (which includes the config). We already depend on Yarn for development anyway. * Add yarn test-prod to run Jest with production environment * Actually flip the production tests to run in prod environment This produces a bunch of errors: Test Suites: 64 failed, 58 passed, 122 total Tests: 740 failed, 26 skipped, 1809 passed, 2575 total Snapshots: 16 failed, 4 passed, 20 total * Ignore expectDev() calls in production Down from 740 to 175 failed. Test Suites: 44 failed, 78 passed, 122 total Tests: 175 failed, 26 skipped, 2374 passed, 2575 total Snapshots: 16 failed, 4 passed, 20 total * Decode errors so tests can assert on their messages Down from 175 to 129. Test Suites: 33 failed, 89 passed, 122 total Tests: 129 failed, 1029 skipped, 1417 passed, 2575 total Snapshots: 16 failed, 4 passed, 20 total * Remove ReactDOMProduction-test There is no need for it now. The only test that was special is moved into ReactDOM-test. * Remove production switches from ReactErrorUtils The tests now run in production in a separate pass. * Add and use spyOnDev() for warnings This ensures that by default we expect no warnings in production bundles. If the warning *is* expected, use the regular spyOn() method. This currently breaks all expectDev() assertions without __DEV__ blocks so we go back to: Test Suites: 56 failed, 65 passed, 121 total Tests: 379 failed, 1029 skipped, 1148 passed, 2556 total Snapshots: 16 failed, 4 passed, 20 total * Replace expectDev() with expect() in __DEV__ blocks We started using spyOnDev() for console warnings to ensure we don't *expect* them to occur in production. As a consequence, expectDev() assertions on console.error.calls fail because console.error.calls doesn't exist. This is actually good because it would help catch accidental warnings in production. To solve this, we are getting rid of expectDev() altogether, and instead introduce explicit expectation branches. We'd need them anyway for testing intentional behavior differences. This commit replaces all expectDev() calls with expect() calls in __DEV__ blocks. It also removes a few unnecessary expect() checks that no warnings were produced (by also removing the corresponding spyOnDev() calls). Some DEV-only assertions used plain expect(). Those were also moved into __DEV__ blocks. ReactFiberErrorLogger was special because it console.error()'s in production too. So in that case I intentionally used spyOn() instead of spyOnDev(), and added extra assertions. This gets us down to: Test Suites: 21 failed, 100 passed, 121 total Tests: 72 failed, 26 skipped, 2458 passed, 2556 total Snapshots: 16 failed, 4 passed, 20 total * Enable User Timing API for production testing We could've disabled it, but seems like a good idea to test since we use it at FB. * Test for explicit Object.freeze() differences between PROD and DEV This is one of the few places where DEV and PROD behavior differs for performance reasons. Now we explicitly test both branches. * Run Jest via "yarn test" on CI * Remove unused variable * Assert different error messages * Fix error handling tests This logic is really complicated because of the global ReactFiberErrorLogger mock. I understand it now, so I added TODOs for later. It can be much simpler if we change the rest of the tests that assert uncaught errors to also assert they are logged as warnings. Which mirrors what happens in practice anyway. * Fix more assertions * Change tests to document the DEV/PROD difference for state invariant It is very likely unintentional but I don't want to change behavior in this PR. Filed a follow up as https://github.com/facebook/react/issues/11618. * Remove unnecessary split between DEV/PROD ref tests * Fix more test message assertions * Make validateDOMNesting tests DEV-only * Fix error message assertions * Document existing DEV/PROD message difference (possible bug) * Change mocking assertions to be DEV-only * Fix the error code test * Fix more error message assertions * Fix the last failing test due to known issue * Run production tests on CI * Unify configuration * Fix coverage script * Remove expectDev from eslintrc * Run everything in band We used to before, too. I just forgot to add the arguments after deleting the script.
This commit is contained in:
+1
-1
@@ -59,6 +59,6 @@ module.exports = {
|
||||
},
|
||||
|
||||
globals: {
|
||||
expectDev: true,
|
||||
spyOnDev: true,
|
||||
},
|
||||
};
|
||||
|
||||
+3
-1
@@ -45,6 +45,7 @@
|
||||
"core-js": "^2.2.1",
|
||||
"coveralls": "^2.11.6",
|
||||
"create-react-class": "^15.6.2",
|
||||
"cross-env": "^5.1.1",
|
||||
"del": "^2.0.2",
|
||||
"derequire": "^2.0.3",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
@@ -102,7 +103,8 @@
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
"lint": "node ./scripts/tasks/eslint.js",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
|
||||
"test": "jest",
|
||||
"test": "cross-env NODE_ENV=development jest",
|
||||
"test-prod": "cross-env NODE_ENV=production jest",
|
||||
"flow": "node ./scripts/tasks/flow.js",
|
||||
"prettier": "node ./scripts/prettier/index.js write-changed",
|
||||
"prettier-all": "node ./scripts/prettier/index.js write",
|
||||
|
||||
+74
-62
@@ -91,15 +91,17 @@ describe('CSSPropertyOperations', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var root = document.createElement('div');
|
||||
ReactDOM.render(<Comp />, root);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: Unsupported style property background-color. Did you mean backgroundColor?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: Unsupported style property background-color. Did you mean backgroundColor?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when updating hyphenated style names', () => {
|
||||
@@ -111,7 +113,7 @@ describe('CSSPropertyOperations', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var styles = {
|
||||
'-ms-transform': 'translate3d(0, 0, 0)',
|
||||
'-webkit-transform': 'translate3d(0, 0, 0)',
|
||||
@@ -120,17 +122,19 @@ describe('CSSPropertyOperations', () => {
|
||||
ReactDOM.render(<Comp />, root);
|
||||
ReactDOM.render(<Comp style={styles} />, root);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
|
||||
'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: Unsupported style property -ms-transform. Did you mean msTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
|
||||
'Warning: Unsupported style property -webkit-transform. Did you mean WebkitTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns when miscapitalizing vendored style names', () => {
|
||||
@@ -150,23 +154,25 @@ describe('CSSPropertyOperations', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var root = document.createElement('div');
|
||||
ReactDOM.render(<Comp />, root);
|
||||
// msTransform is correct already and shouldn't warn
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: Unsupported vendor-prefixed style property oTransform. ' +
|
||||
'Did you mean OTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
|
||||
'Warning: Unsupported vendor-prefixed style property webkitTransform. ' +
|
||||
'Did you mean WebkitTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
// msTransform is correct already and shouldn't warn
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: Unsupported vendor-prefixed style property oTransform. ' +
|
||||
'Did you mean OTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
|
||||
'Warning: Unsupported vendor-prefixed style property webkitTransform. ' +
|
||||
'Did you mean WebkitTransform?' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn about style having a trailing semicolon', () => {
|
||||
@@ -187,22 +193,24 @@ describe('CSSPropertyOperations', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var root = document.createElement('div');
|
||||
ReactDOM.render(<Comp />, root);
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
"Warning: Style property values shouldn't contain a semicolon. " +
|
||||
'Try "backgroundColor: blue" instead.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
|
||||
"Warning: Style property values shouldn't contain a semicolon. " +
|
||||
'Try "color: red" instead.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
"Warning: Style property values shouldn't contain a semicolon. " +
|
||||
'Try "backgroundColor: blue" instead.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toEqual(
|
||||
"Warning: Style property values shouldn't contain a semicolon. " +
|
||||
'Try "color: red" instead.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn about style containing a NaN value', () => {
|
||||
@@ -214,16 +222,18 @@ describe('CSSPropertyOperations', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var root = document.createElement('div');
|
||||
ReactDOM.render(<Comp />, root);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not warn when setting CSS custom properties', () => {
|
||||
@@ -246,16 +256,18 @@ describe('CSSPropertyOperations', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var root = document.createElement('div');
|
||||
ReactDOM.render(<Comp />, root);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toEqual(
|
||||
'Warning: `Infinity` is an invalid value for the `fontSize` css style property.' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Comp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not add units to CSS custom properties', () => {
|
||||
|
||||
@@ -151,7 +151,7 @@ describe('DOMPropertyOperations', () => {
|
||||
|
||||
it('should not remove attributes for special properties', () => {
|
||||
var container = document.createElement('div');
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.render(
|
||||
<input type="text" value="foo" onChange={function() {}} />,
|
||||
container,
|
||||
@@ -164,10 +164,12 @@ describe('DOMPropertyOperations', () => {
|
||||
);
|
||||
expect(container.firstChild.getAttribute('value')).toBe('foo');
|
||||
expect(container.firstChild.value).toBe('foo');
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'A component is changing a controlled input of type text to be uncontrolled',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'A component is changing a controlled input of type text to be uncontrolled',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+7
-5
@@ -22,7 +22,7 @@ describe('EventPluginHub', () => {
|
||||
});
|
||||
|
||||
it('should prevent non-function listeners, at dispatch', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node = ReactTestUtils.renderIntoDocument(
|
||||
<div onClick="not a function" />,
|
||||
);
|
||||
@@ -31,10 +31,12 @@ describe('EventPluginHub', () => {
|
||||
}).toThrowError(
|
||||
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Expected `onClick` listener to be a function, instead got a value of `string` type.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not prevent null listeners, at dispatch', () => {
|
||||
|
||||
@@ -285,13 +285,15 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(idCallOrder.length).toBe(3);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
expect(idCallOrder[2]).toBe(GRANDPARENT);
|
||||
expectDev(console.error.calls.count()).toEqual(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toEqual(0);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
+50
-46
@@ -46,7 +46,7 @@ describe('ReactChildReconciler', () => {
|
||||
}
|
||||
|
||||
it('warns for duplicated array keys', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
@@ -56,17 +56,19 @@ describe('ReactChildReconciler', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for duplicated array keys with component stack info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
@@ -88,24 +90,24 @@ describe('ReactChildReconciler', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<GrandParent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for duplicated iterable keys', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
@@ -115,17 +117,19 @@ describe('ReactChildReconciler', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for duplicated iterable keys with component stack info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
@@ -147,19 +151,19 @@ describe('ReactChildReconciler', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<GrandParent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+126
-84
@@ -45,13 +45,23 @@ describe('ReactComponent', () => {
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should warn when children are mutated during render', () => {
|
||||
spyOn(console, 'error');
|
||||
it('should throw (in dev) when children are mutated during render', () => {
|
||||
spyOnDev(console, 'error');
|
||||
function Wrapper(props) {
|
||||
props.children[1] = <p key={1} />; // Mutation is illegal
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
} else {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
@@ -59,11 +69,11 @@ describe('ReactComponent', () => {
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when children are mutated during update', () => {
|
||||
spyOn(console, 'error');
|
||||
it('should throw (in dev) when children are mutated during update', () => {
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
componentDidMount() {
|
||||
@@ -76,7 +86,17 @@ describe('ReactComponent', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
} else {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
@@ -84,7 +104,7 @@ describe('ReactComponent', () => {
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support refs on owned components', () => {
|
||||
@@ -335,14 +355,16 @@ describe('ReactComponent', () => {
|
||||
});
|
||||
|
||||
it('throws usefully when rendering badly-typed elements', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var X = undefined;
|
||||
expect(() => ReactTestUtils.renderIntoDocument(<X />)).toThrowError(
|
||||
'Element type is invalid: expected a string (for built-in components) ' +
|
||||
'or a class/function (for composite components) but got: undefined. ' +
|
||||
"You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.',
|
||||
'or a class/function (for composite components) but got: undefined.' +
|
||||
(__DEV__
|
||||
? " You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.'
|
||||
: ''),
|
||||
);
|
||||
|
||||
var Y = null;
|
||||
@@ -351,12 +373,14 @@ describe('ReactComponent', () => {
|
||||
'or a class/function (for composite components) but got: null.',
|
||||
);
|
||||
|
||||
// One warning for each element creation
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
if (__DEV__) {
|
||||
// One warning for each element creation
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it('includes owner name in the error about badly-typed elements', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var X = undefined;
|
||||
|
||||
@@ -378,14 +402,18 @@ describe('ReactComponent', () => {
|
||||
|
||||
expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toThrowError(
|
||||
'Element type is invalid: expected a string (for built-in components) ' +
|
||||
'or a class/function (for composite components) but got: undefined. ' +
|
||||
"You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.' +
|
||||
'\n\nCheck the render method of `Bar`.',
|
||||
'or a class/function (for composite components) but got: undefined.' +
|
||||
(__DEV__
|
||||
? " You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.' +
|
||||
'\n\nCheck the render method of `Bar`.'
|
||||
: ''),
|
||||
);
|
||||
|
||||
// One warning for each element creation
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
// One warning for each element creation
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws if a plain object is used as a child', () => {
|
||||
@@ -404,10 +432,12 @@ describe('ReactComponent', () => {
|
||||
}
|
||||
expect(ex).toBeDefined();
|
||||
expect(normalizeCodeLocInfo(ex.message)).toBe(
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{x, y, z}). If you meant to render a collection of children, use ' +
|
||||
'an array instead.' +
|
||||
'\n in div (at **)',
|
||||
'Objects are not valid as a React child (found: object with keys {x, y, z}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use ' +
|
||||
'an array instead.' +
|
||||
'\n in div (at **)'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -431,11 +461,13 @@ describe('ReactComponent', () => {
|
||||
}
|
||||
expect(ex).toBeDefined();
|
||||
expect(normalizeCodeLocInfo(ex.message)).toBe(
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{a, b, c}). If you meant to render a collection of children, use ' +
|
||||
'an array instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
'Objects are not valid as a React child (found: object with keys {a, b, c}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use ' +
|
||||
'an array instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -454,10 +486,12 @@ describe('ReactComponent', () => {
|
||||
}
|
||||
expect(ex).toBeDefined();
|
||||
expect(normalizeCodeLocInfo(ex.message)).toBe(
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{x, y, z}). If you meant to render a collection of children, use ' +
|
||||
'an array instead.' +
|
||||
'\n in div (at **)',
|
||||
'Objects are not valid as a React child (found: object with keys {x, y, z}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use ' +
|
||||
'an array instead.' +
|
||||
'\n in div (at **)'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -481,11 +515,13 @@ describe('ReactComponent', () => {
|
||||
}
|
||||
expect(ex).toBeDefined();
|
||||
expect(normalizeCodeLocInfo(ex.message)).toBe(
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{a, b, c}). If you meant to render a collection of children, use ' +
|
||||
'an array instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
'Objects are not valid as a React child (found: object with keys {a, b, c}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use ' +
|
||||
'an array instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -494,16 +530,18 @@ describe('ReactComponent', () => {
|
||||
function Foo() {
|
||||
return Foo;
|
||||
}
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Foo />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns on function as a return value from a class', () => {
|
||||
@@ -512,16 +550,18 @@ describe('ReactComponent', () => {
|
||||
return Foo;
|
||||
}
|
||||
}
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Foo />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns on function as a child to host component', () => {
|
||||
@@ -532,18 +572,20 @@ describe('ReactComponent', () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Foo />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn for function-as-a-child that gets resolved', () => {
|
||||
@@ -553,15 +595,13 @@ describe('ReactComponent', () => {
|
||||
function Foo() {
|
||||
return <Bar>{() => 'Hello'}</Bar>;
|
||||
}
|
||||
spyOn(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Foo />, container);
|
||||
expect(container.innerHTML).toBe('Hello');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('deduplicates function type warnings based on component type', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Foo extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -583,22 +623,24 @@ describe('ReactComponent', () => {
|
||||
var container = document.createElement('div');
|
||||
var component = ReactDOM.render(<Foo />, container);
|
||||
component.setState({type: 'portobello mushrooms'});
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
});
|
||||
|
||||
it('should not allow update state inside of getInitialState', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class StatefulComponent extends React.Component {
|
||||
constructor(props, context) {
|
||||
@@ -214,21 +214,25 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<StatefulComponent />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: setState(...): Can only update a mounted or ' +
|
||||
'mounting component. This usually means you called setState() on an ' +
|
||||
'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'StatefulComponent component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: setState(...): Can only update a mounted or ' +
|
||||
'mounting component. This usually means you called setState() on an ' +
|
||||
'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'StatefulComponent component.',
|
||||
);
|
||||
}
|
||||
|
||||
// Check deduplication
|
||||
ReactTestUtils.renderIntoDocument(<StatefulComponent />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly determine if a component is mounted', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Component extends React.Component {
|
||||
_isMounted() {
|
||||
// No longer a public API, but we can test that it works internally by
|
||||
@@ -252,14 +256,16 @@ describe('ReactComponentLifeCycle', () => {
|
||||
var instance = ReactTestUtils.renderIntoDocument(element);
|
||||
expect(instance._isMounted()).toBeTruthy();
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Component is accessing isMounted inside its render()',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Component is accessing isMounted inside its render()',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly determine if a null component is mounted', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Component extends React.Component {
|
||||
_isMounted() {
|
||||
// No longer a public API, but we can test that it works internally by
|
||||
@@ -283,10 +289,12 @@ describe('ReactComponentLifeCycle', () => {
|
||||
var instance = ReactTestUtils.renderIntoDocument(element);
|
||||
expect(instance._isMounted()).toBeTruthy();
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Component is accessing isMounted inside its render()',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Component is accessing isMounted inside its render()',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('isMounted should return false when unmounted', () => {
|
||||
@@ -309,7 +317,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
});
|
||||
|
||||
it('warns if findDOMNode is used inside render', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Component extends React.Component {
|
||||
state = {isMounted: false};
|
||||
componentDidMount() {
|
||||
@@ -324,14 +332,16 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Component is accessing findDOMNode inside its render()',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Component is accessing findDOMNode inside its render()',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should carry through each of the phases of setup', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class LifeCycleComponent extends React.Component {
|
||||
constructor(props, context) {
|
||||
@@ -440,10 +450,12 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(getLifeCycleState(instance)).toBe('UNMOUNTED');
|
||||
expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'LifeCycleComponent is accessing isMounted inside its render() function',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'LifeCycleComponent is accessing isMounted inside its render() function',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not throw when updating an auxiliary component', () => {
|
||||
|
||||
+156
-109
@@ -122,26 +122,29 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
var markup = ReactDOMServer.renderToString(<Parent />);
|
||||
|
||||
// Old API based on heuristic
|
||||
var container = document.createElement('div');
|
||||
container.innerHTML = markup;
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expectDev(console.warn.calls.count()).toBe(1);
|
||||
expectDev(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(1);
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
console.warn.calls.reset();
|
||||
}
|
||||
// New explicit API
|
||||
console.warn.calls.reset();
|
||||
container = document.createElement('div');
|
||||
container.innerHTML = markup;
|
||||
ReactDOM.hydrate(<Parent />, container);
|
||||
expectDev(console.warn.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should react to state changes from callbacks', () => {
|
||||
@@ -231,7 +234,7 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should warn about `forceUpdate` on unmounted components', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -248,25 +251,31 @@ describe('ReactCompositeComponent', () => {
|
||||
instance = ReactDOM.render(instance, container);
|
||||
instance.forceUpdate();
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
|
||||
instance.forceUpdate();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Can only update a mounted or mounting component. This usually means ' +
|
||||
'you called setState, replaceState, or forceUpdate on an unmounted ' +
|
||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'Component component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Can only update a mounted or mounting component. This usually means ' +
|
||||
'you called setState, replaceState, or forceUpdate on an unmounted ' +
|
||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'Component component.',
|
||||
);
|
||||
}
|
||||
|
||||
instance.forceUpdate();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn about `setState` on unmounted components', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -291,7 +300,9 @@ describe('ReactCompositeComponent', () => {
|
||||
|
||||
instance.setState({value: 1});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
expect(renders).toBe(2);
|
||||
|
||||
@@ -300,13 +311,15 @@ describe('ReactCompositeComponent', () => {
|
||||
|
||||
expect(renders).toBe(2);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Can only update a mounted or mounting component. This usually means ' +
|
||||
'you called setState, replaceState, or forceUpdate on an unmounted ' +
|
||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'Component component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Can only update a mounted or mounting component. This usually means ' +
|
||||
'you called setState, replaceState, or forceUpdate on an unmounted ' +
|
||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'Component component.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should silently allow `setState`, not call cb on unmounting components', () => {
|
||||
@@ -338,27 +351,31 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should warn when rendering a class with a render method that does not extend React.Component', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
class ClassWithRenderNotExtended {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
expect(() => {
|
||||
ReactDOM.render(<ClassWithRenderNotExtended />, container);
|
||||
}).toThrow(TypeError);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: The <ClassWithRenderNotExtended /> component appears to have a render method, ' +
|
||||
"but doesn't extend React.Component. This is likely to cause errors. " +
|
||||
'Change ClassWithRenderNotExtended to extend React.Component instead.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: The <ClassWithRenderNotExtended /> component appears to have a render method, ' +
|
||||
"but doesn't extend React.Component. This is likely to cause errors. " +
|
||||
'Change ClassWithRenderNotExtended to extend React.Component instead.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn about `setState` in render', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
|
||||
@@ -378,17 +395,21 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
var instance = ReactDOM.render(<Component />, container);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Cannot update during an existing state transition (such as within ' +
|
||||
"`render` or another component's constructor). Render methods should " +
|
||||
'be a pure function of props and state; constructor side-effects are ' +
|
||||
'an anti-pattern, but can be moved to `componentWillMount`.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Cannot update during an existing state transition (such as within ' +
|
||||
"`render` or another component's constructor). Render methods should " +
|
||||
'be a pure function of props and state; constructor side-effects are ' +
|
||||
'an anti-pattern, but can be moved to `componentWillMount`.',
|
||||
);
|
||||
}
|
||||
|
||||
// The setState call is queued and then executed as a second pass. This
|
||||
// behavior is undefined though so we're free to change it to suit the
|
||||
@@ -406,11 +427,13 @@ describe('ReactCompositeComponent', () => {
|
||||
// Test deduplication
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
ReactDOM.render(<Component prop={123} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn about `setState` in getChildContext', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
|
||||
@@ -432,19 +455,25 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
Component.childContextTypes = {};
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
var instance = ReactDOM.render(<Component />, container);
|
||||
expect(renderPasses).toBe(2);
|
||||
expect(instance.state.value).toBe(1);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: setState(...): Cannot call setState() inside getChildContext()',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: setState(...): Cannot call setState() inside getChildContext()',
|
||||
);
|
||||
}
|
||||
|
||||
// Test deduplication
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
ReactDOM.render(<Component />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should cleanup even if render() fatals', () => {
|
||||
@@ -496,7 +525,7 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should warn when shouldComponentUpdate() returns undefined', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
state = {bogus: false};
|
||||
@@ -513,15 +542,17 @@ describe('ReactCompositeComponent', () => {
|
||||
var instance = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
instance.setState({bogus: true});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' +
|
||||
'boolean value. Make sure to return true or false.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' +
|
||||
'boolean value. Make sure to return true or false.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when componentDidUnmount method is defined', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
componentDidUnmount = () => {};
|
||||
@@ -533,16 +564,18 @@ describe('ReactCompositeComponent', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component has a method called ' +
|
||||
'componentDidUnmount(). But there is no such lifecycle method. ' +
|
||||
'Did you mean componentWillUnmount()?',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component has a method called ' +
|
||||
'componentDidUnmount(). But there is no such lifecycle method. ' +
|
||||
'Did you mean componentWillUnmount()?',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when componentDidReceiveProps method is defined', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
componentDidReceiveProps = () => {};
|
||||
@@ -554,18 +587,20 @@ describe('ReactCompositeComponent', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component has a method called ' +
|
||||
'componentDidReceiveProps(). But there is no such lifecycle method. ' +
|
||||
'If you meant to update the state in response to changing props, ' +
|
||||
'use componentWillReceiveProps(). If you meant to fetch data or ' +
|
||||
'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component has a method called ' +
|
||||
'componentDidReceiveProps(). But there is no such lifecycle method. ' +
|
||||
'If you meant to update the state in response to changing props, ' +
|
||||
'use componentWillReceiveProps(). If you meant to fetch data or ' +
|
||||
'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when defaultProps was defined as an instance property', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -580,11 +615,13 @@ describe('ReactCompositeComponent', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Setting defaultProps as an instance property on Component is not supported ' +
|
||||
'and will be ignored. Instead, define defaultProps as a static property on Component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Setting defaultProps as an instance property on Component is not supported ' +
|
||||
'and will be ignored. Instead, define defaultProps as a static property on Component.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should pass context to children when not owner', () => {
|
||||
@@ -1056,7 +1093,7 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should disallow nested render calls', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Inner extends React.Component {
|
||||
render() {
|
||||
@@ -1072,13 +1109,15 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Outer />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toMatch(
|
||||
'Render methods should be a pure function of props and state; ' +
|
||||
'triggering nested component updates from render is not allowed. If ' +
|
||||
'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' +
|
||||
'render method of Outer.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toMatch(
|
||||
'Render methods should be a pure function of props and state; ' +
|
||||
'triggering nested component updates from render is not allowed. If ' +
|
||||
'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' +
|
||||
'render method of Outer.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('only renders once if updated in componentWillReceiveProps', () => {
|
||||
@@ -1353,7 +1392,7 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should warn when mutated props are passed', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
|
||||
@@ -1368,15 +1407,19 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Foo idx="qwe" />, container);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Foo(...): When calling super() in `Foo`, make sure to pass ' +
|
||||
"up the same props that your component's constructor was passed.",
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Foo(...): When calling super() in `Foo`, make sure to pass ' +
|
||||
"up the same props that your component's constructor was passed.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should only call componentWillUnmount once', () => {
|
||||
@@ -1592,7 +1635,7 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should return a meaningful warning when constructor is returned', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class RenderTextInvalidConstructor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -1608,25 +1651,29 @@ describe('ReactCompositeComponent', () => {
|
||||
ReactTestUtils.renderIntoDocument(<RenderTextInvalidConstructor />);
|
||||
}).toThrow();
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: RenderTextInvalidConstructor(...): No `render` method found on the returned component instance: ' +
|
||||
'did you accidentally return an object from the constructor?',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: RenderTextInvalidConstructor(...): No `render` method found on the returned component instance: ' +
|
||||
'did you accidentally return an object from the constructor?',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return error if render is not defined', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class RenderTestUndefinedRender extends React.Component {}
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<RenderTestUndefinedRender />);
|
||||
}).toThrow();
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: RenderTestUndefinedRender(...): No `render` method found on the returned ' +
|
||||
'component instance: you may have forgotten to define `render`.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: RenderTestUndefinedRender(...): No `render` method found on the returned ' +
|
||||
'component instance: you may have forgotten to define `render`.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -381,7 +381,7 @@ describe('ReactCompositeComponent-state', () => {
|
||||
});
|
||||
|
||||
it('should treat assigning to this.state inside cWRP as a replaceState, with a warning', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
let ops = [];
|
||||
class Test extends React.Component {
|
||||
@@ -416,20 +416,24 @@ describe('ReactCompositeComponent-state', () => {
|
||||
'render -- step: 3, extra: false',
|
||||
'callback -- step: 3, extra: false',
|
||||
]);
|
||||
expect(console.error.calls.count()).toEqual(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toEqual(
|
||||
'Warning: Test.componentWillReceiveProps(): Assigning directly to ' +
|
||||
"this.state is deprecated (except inside a component's constructor). " +
|
||||
'Use setState instead.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toEqual(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toEqual(
|
||||
'Warning: Test.componentWillReceiveProps(): Assigning directly to ' +
|
||||
"this.state is deprecated (except inside a component's constructor). " +
|
||||
'Use setState instead.',
|
||||
);
|
||||
}
|
||||
|
||||
// Check deduplication
|
||||
ReactDOM.render(<Test />, container);
|
||||
expect(console.error.calls.count()).toEqual(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toEqual(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
let ops = [];
|
||||
class Test extends React.Component {
|
||||
@@ -461,11 +465,13 @@ describe('ReactCompositeComponent-state', () => {
|
||||
'render -- step: 3, extra: false',
|
||||
'callback -- step: 3, extra: false',
|
||||
]);
|
||||
expect(console.error.calls.count()).toEqual(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toEqual(
|
||||
'Warning: Test.componentWillMount(): Assigning directly to ' +
|
||||
"this.state is deprecated (except inside a component's constructor). " +
|
||||
'Use setState instead.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toEqual(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toEqual(
|
||||
'Warning: Test.componentWillMount(): Assigning directly to ' +
|
||||
"this.state is deprecated (except inside a component's constructor). " +
|
||||
'Use setState instead.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+62
-28
@@ -109,7 +109,7 @@ describe('ReactDOM', () => {
|
||||
});
|
||||
|
||||
it('throws in render() if the mount callback is not a function', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Foo() {
|
||||
this.a = 1;
|
||||
@@ -129,31 +129,37 @@ describe('ReactDOM', () => {
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
}
|
||||
expect(() => ReactDOM.render(<A />, myDiv, {foo: 'bar'})).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
}
|
||||
expect(() => ReactDOM.render(<A />, myDiv, new Foo())).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws in render() if the update callback is not a function', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Foo() {
|
||||
this.a = 1;
|
||||
@@ -174,29 +180,35 @@ describe('ReactDOM', () => {
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
}
|
||||
ReactDOM.render(<A />, myDiv); // Re-mount
|
||||
expect(() => ReactDOM.render(<A />, myDiv, {foo: 'bar'})).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
}
|
||||
ReactDOM.render(<A />, myDiv); // Re-mount
|
||||
expect(() => ReactDOM.render(<A />, myDiv, new Foo())).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'render(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves focus', () => {
|
||||
@@ -338,4 +350,26 @@ describe('ReactDOM', () => {
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not crash with devtools installed', () => {
|
||||
try {
|
||||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
inject: function() {},
|
||||
onCommitFiberRoot: function() {},
|
||||
onCommitFiberUnmount: function() {},
|
||||
supportsFiber: true,
|
||||
};
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Component />, document.createElement('container'));
|
||||
} finally {
|
||||
delete global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+52
-48
@@ -46,20 +46,20 @@ describe('ReactDOM unknown attribute', () => {
|
||||
});
|
||||
|
||||
it('changes values true, false to null, and also warns once', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
testUnknownAttributeAssignment(true, null);
|
||||
testUnknownAttributeAssignment(false, null);
|
||||
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toMatch(
|
||||
'Received `true` for a non-boolean attribute `unknown`.\n\n' +
|
||||
'If you want to write it to the DOM, pass a string instead: ' +
|
||||
'unknown="true" or unknown={value.toString()}.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
|
||||
'Received `true` for a non-boolean attribute `unknown`.\n\n' +
|
||||
'If you want to write it to the DOM, pass a string instead: ' +
|
||||
'unknown="true" or unknown={value.toString()}.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('removes unknown attributes that were rendered but are now missing', () => {
|
||||
@@ -82,17 +82,17 @@ describe('ReactDOM unknown attribute', () => {
|
||||
});
|
||||
|
||||
it('coerces NaN to strings and warns', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
testUnknownAttributeAssignment(NaN, 'NaN');
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toMatch(
|
||||
'Warning: Received NaN for the `unknown` attribute. ' +
|
||||
'If this is expected, cast the value to a string.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
|
||||
'Warning: Received NaN for the `unknown` attribute. ' +
|
||||
'If this is expected, cast the value to a string.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('coerces objects to strings and warns', () => {
|
||||
@@ -107,50 +107,54 @@ describe('ReactDOM unknown attribute', () => {
|
||||
});
|
||||
|
||||
it('removes symbols and warns', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
testUnknownAttributeRemoval(Symbol('foo'));
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Invalid value for prop `unknown` on <div> tag. Either remove it ' +
|
||||
'from the element, or pass a string or number value to keep it ' +
|
||||
'in the DOM. For details, see https://fb.me/react-attribute-behavior\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Invalid value for prop `unknown` on <div> tag. Either remove it ' +
|
||||
'from the element, or pass a string or number value to keep it ' +
|
||||
'in the DOM. For details, see https://fb.me/react-attribute-behavior\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('removes functions and warns', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
testUnknownAttributeRemoval(function someFunction() {});
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Invalid value for prop `unknown` on <div> tag. Either remove ' +
|
||||
'it from the element, or pass a string or number value to ' +
|
||||
'keep it in the DOM. For details, see ' +
|
||||
'https://fb.me/react-attribute-behavior\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Invalid value for prop `unknown` on <div> tag. Either remove ' +
|
||||
'it from the element, or pass a string or number value to ' +
|
||||
'keep it in the DOM. For details, see ' +
|
||||
'https://fb.me/react-attribute-behavior\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('allows camelCase unknown attributes and warns', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var el = document.createElement('div');
|
||||
ReactDOM.render(<div helloWorld="something" />, el);
|
||||
expect(el.firstChild.getAttribute('helloworld')).toBe('something');
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toMatch(
|
||||
'React does not recognize the `helloWorld` prop on a DOM element. ' +
|
||||
'If you intentionally want it to appear in the DOM as a custom ' +
|
||||
'attribute, spell it as lowercase `helloworld` instead. ' +
|
||||
'If you accidentally passed it from a parent component, remove ' +
|
||||
'it from the DOM element.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toMatch(
|
||||
'React does not recognize the `helloWorld` prop on a DOM element. ' +
|
||||
'If you intentionally want it to appear in the DOM as a custom ' +
|
||||
'attribute, spell it as lowercase `helloworld` instead. ' +
|
||||
'If you accidentally passed it from a parent component, remove ' +
|
||||
'it from the DOM element.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+628
-479
File diff suppressed because it is too large
Load Diff
+35
-27
@@ -177,36 +177,42 @@ describe('ReactDOMComponentTree', () => {
|
||||
|
||||
const component = <Controlled />;
|
||||
const instance = ReactDOM.render(component, container);
|
||||
spyOn(console, 'error');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
spyOnDev(console, 'error');
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
simulateInput(instance.a, finishValue);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: A component is changing an uncontrolled input of ' +
|
||||
'type text to be controlled. Input elements should not ' +
|
||||
'switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: A component is changing an uncontrolled input of ' +
|
||||
'type text to be controlled. Input elements should not ' +
|
||||
'switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('finds instance of node that is attempted to be unmounted', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
const component = <div />;
|
||||
const node = ReactDOM.render(<div>{component}</div>, container);
|
||||
ReactDOM.unmountComponentAtNode(node);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
"unmountComponentAtNode(): The node you're attempting to unmount " +
|
||||
'was rendered by React and is not a top-level container. You may ' +
|
||||
'have accidentally passed in a React root node instead of its ' +
|
||||
'container.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
"unmountComponentAtNode(): The node you're attempting to unmount " +
|
||||
'was rendered by React and is not a top-level container. You may ' +
|
||||
'have accidentally passed in a React root node instead of its ' +
|
||||
'container.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('finds instance from node to stop rendering over other react rendered components', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
const component = (
|
||||
<div>
|
||||
<span>Hello</span>
|
||||
@@ -215,12 +221,14 @@ describe('ReactDOMComponentTree', () => {
|
||||
const anotherComponent = <div />;
|
||||
const instance = ReactDOM.render(component, container);
|
||||
ReactDOM.render(anotherComponent, instance);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): Replacing React-rendered children with a new root ' +
|
||||
'component. If you intended to update the children of this node, ' +
|
||||
'you should instead have the existing children update their state ' +
|
||||
'and render the new components instead of calling ReactDOM.render.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): Replacing React-rendered children with a new root ' +
|
||||
'component. If you intended to update the children of this node, ' +
|
||||
'you should instead have the existing children update their state ' +
|
||||
'and render the new components instead of calling ReactDOM.render.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+59
-49
@@ -919,40 +919,40 @@ describe('ReactDOMFiber', () => {
|
||||
});
|
||||
|
||||
it('should warn for non-functional event listeners', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Example extends React.Component {
|
||||
render() {
|
||||
return <div onClick="woops" />;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Example />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Example (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
|
||||
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Example (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn with a special message for `false` event listeners', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Example extends React.Component {
|
||||
render() {
|
||||
return <div onClick={false} />;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Example />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
|
||||
'If you used to conditionally omit it with onClick={condition && value}, ' +
|
||||
'pass onClick={condition ? value : undefined} instead.\n',
|
||||
' in div (at **)\n' + ' in Example (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
|
||||
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
|
||||
'If you used to conditionally omit it with onClick={condition && value}, ' +
|
||||
'pass onClick={condition ? value : undefined} instead.\n',
|
||||
' in div (at **)\n' + ' in Example (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not update event handlers until commit', () => {
|
||||
@@ -1048,19 +1048,23 @@ describe('ReactDOMFiber', () => {
|
||||
});
|
||||
|
||||
it('should not warn when rendering into an empty container', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.render(<div>foo</div>, container);
|
||||
expect(container.innerHTML).toBe('<div>foo</div>');
|
||||
ReactDOM.render(null, container);
|
||||
expect(container.innerHTML).toBe('');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
ReactDOM.render(<div>bar</div>, container);
|
||||
expect(container.innerHTML).toBe('<div>bar</div>');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when replacing a container which was manually updated outside of React', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// when not messing with the DOM outside of React
|
||||
ReactDOM.render(<div key="1">foo</div>, container);
|
||||
ReactDOM.render(<div key="1">bar</div>, container);
|
||||
@@ -1072,18 +1076,20 @@ describe('ReactDOMFiber', () => {
|
||||
container.innerHTML = '<div>MEOW.</div>';
|
||||
ReactDOM.render(<div key="2">baz</div>, container);
|
||||
}).toThrowError();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): ' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): ' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when doing an update to a container manually updated outside of React', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// when not messing with the DOM outside of React
|
||||
ReactDOM.render(<div>foo</div>, container);
|
||||
ReactDOM.render(<div>bar</div>, container);
|
||||
@@ -1092,18 +1098,20 @@ describe('ReactDOMFiber', () => {
|
||||
container.innerHTML = '<div>MEOW.</div>';
|
||||
ReactDOM.render(<div>baz</div>, container);
|
||||
// silently fails to update
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): ' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): ' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when doing an update to a container manually cleared outside of React', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// when not messing with the DOM outside of React
|
||||
ReactDOM.render(<div>foo</div>, container);
|
||||
ReactDOM.render(<div>bar</div>, container);
|
||||
@@ -1112,14 +1120,16 @@ describe('ReactDOMFiber', () => {
|
||||
container.innerHTML = '';
|
||||
ReactDOM.render(<div>baz</div>, container);
|
||||
// silently fails to update
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): ' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'render(...): ' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should render a text component with a text DOM node on the same document as the container', () => {
|
||||
|
||||
+224
-166
@@ -38,7 +38,7 @@ describe('ReactDOMInput', () => {
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
});
|
||||
|
||||
it('should properly control a value even if no event listener exists', () => {
|
||||
@@ -48,7 +48,9 @@ describe('ReactDOMInput', () => {
|
||||
document.body.appendChild(container);
|
||||
|
||||
var node = ReactDOM.findDOMNode(stub);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
|
||||
setUntrackedValue.call(node, 'giraffe');
|
||||
|
||||
@@ -785,19 +787,23 @@ describe('ReactDOMInput', () => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" value="zoink" readOnly={true} />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" value="zoink" readOnly={false} />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: You provided a `value` prop to a form ' +
|
||||
'field without an `onChange` handler. This will render a read-only ' +
|
||||
'field. If the field should be mutable use `defaultValue`. ' +
|
||||
'Otherwise, set either `onChange` or `readOnly`.\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: You provided a `value` prop to a form ' +
|
||||
'field without an `onChange` handler. This will render a read-only ' +
|
||||
'field. If the field should be mutable use `defaultValue`. ' +
|
||||
'Otherwise, set either `onChange` or `readOnly`.\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have a this value of undefined if bind is not used', () => {
|
||||
@@ -815,12 +821,16 @@ describe('ReactDOMInput', () => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<input type="checkbox" checked="false" readOnly={true} />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<input type="checkbox" checked="false" readOnly={false} />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should update defaultValue to empty string', () => {
|
||||
@@ -832,14 +842,18 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
it('should warn if value is null', () => {
|
||||
ReactTestUtils.renderIntoDocument(<input type="text" value={null} />);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `input` should not be null. ' +
|
||||
'Consider using an empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `input` should not be null. ' +
|
||||
'Consider using an empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<input type="text" value={null} />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if checked and defaultChecked props are specified', () => {
|
||||
@@ -851,14 +865,16 @@ describe('ReactDOMInput', () => {
|
||||
readOnly={true}
|
||||
/>,
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'A component contains an input of type radio with both checked and defaultChecked props. ' +
|
||||
'Input elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the checked prop, or the defaultChecked prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled input ' +
|
||||
'element and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'A component contains an input of type radio with both checked and defaultChecked props. ' +
|
||||
'Input elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the checked prop, or the defaultChecked prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled input ' +
|
||||
'element and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<input
|
||||
@@ -868,26 +884,32 @@ describe('ReactDOMInput', () => {
|
||||
readOnly={true}
|
||||
/>,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if value and defaultValue props are specified', () => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" value="foo" defaultValue="bar" readOnly={true} />,
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'A component contains an input of type text with both value and defaultValue props. ' +
|
||||
'Input elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the value prop, or the defaultValue prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled input ' +
|
||||
'element and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'A component contains an input of type text with both value and defaultValue props. ' +
|
||||
'Input elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the value prop, or the defaultValue prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled input ' +
|
||||
'element and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<input type="text" value="foo" defaultValue="bar" readOnly={true} />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled input switches to uncontrolled (value is undefined)', () => {
|
||||
@@ -897,14 +919,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="text" />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled input switches to uncontrolled (value is null)', () => {
|
||||
@@ -914,14 +938,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="text" value={null} />, container);
|
||||
expectDev(console.error.calls.count()).toBeGreaterThan(0);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBeGreaterThan(0);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled input switches to uncontrolled with defaultValue', () => {
|
||||
@@ -934,14 +960,16 @@ describe('ReactDOMInput', () => {
|
||||
<input type="text" defaultValue="uncontrolled" />,
|
||||
container,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type text to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if uncontrolled input (value is undefined) switches to controlled', () => {
|
||||
@@ -949,14 +977,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="text" value="controlled" />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if uncontrolled input (value is null) switches to controlled', () => {
|
||||
@@ -964,14 +994,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="text" value="controlled" />, container);
|
||||
expectDev(console.error.calls.count()).toBeGreaterThan(0);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBeGreaterThan(0);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type text to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled checkbox switches to uncontrolled (checked is undefined)', () => {
|
||||
@@ -981,14 +1013,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="checkbox" />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled checkbox switches to uncontrolled (checked is null)', () => {
|
||||
@@ -998,14 +1032,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="checkbox" checked={null} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled checkbox switches to uncontrolled with defaultChecked', () => {
|
||||
@@ -1015,14 +1051,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="checkbox" defaultChecked={true} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type checkbox to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if uncontrolled checkbox (checked is undefined) switches to controlled', () => {
|
||||
@@ -1030,14 +1068,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="checkbox" checked={true} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if uncontrolled checkbox (checked is null) switches to controlled', () => {
|
||||
@@ -1045,14 +1085,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="checkbox" checked={true} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type checkbox to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled radio switches to uncontrolled (checked is undefined)', () => {
|
||||
@@ -1060,14 +1102,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="radio" />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled radio switches to uncontrolled (checked is null)', () => {
|
||||
@@ -1075,14 +1119,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="radio" checked={null} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if controlled radio switches to uncontrolled with defaultChecked', () => {
|
||||
@@ -1090,14 +1136,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="radio" defaultChecked={true} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if uncontrolled radio (checked is undefined) switches to controlled', () => {
|
||||
@@ -1105,14 +1153,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="radio" checked={true} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if uncontrolled radio (checked is null) switches to controlled', () => {
|
||||
@@ -1120,14 +1170,16 @@ describe('ReactDOMInput', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(stub, container);
|
||||
ReactDOM.render(<input type="radio" checked={true} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing an uncontrolled input of type radio to be controlled. ' +
|
||||
'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not warn if radio value changes but never becomes controlled', () => {
|
||||
@@ -1143,7 +1195,9 @@ describe('ReactDOMInput', () => {
|
||||
container,
|
||||
);
|
||||
ReactDOM.render(<input type="radio" />, container);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not warn if radio value changes but never becomes uncontrolled', () => {
|
||||
@@ -1162,7 +1216,9 @@ describe('ReactDOMInput', () => {
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if radio checked false changes to become uncontrolled', () => {
|
||||
@@ -1177,13 +1233,15 @@ describe('ReactDOMInput', () => {
|
||||
container,
|
||||
);
|
||||
ReactDOM.render(<input type="radio" value="value" />, container);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is changing a controlled input of type radio to be uncontrolled. ' +
|
||||
'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
|
||||
'Decide between using a controlled or uncontrolled input ' +
|
||||
'element for the lifetime of the component. More info: https://fb.me/react-controlled-components\n' +
|
||||
' in input (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('sets type, step, min, max before value always', () => {
|
||||
|
||||
@@ -26,62 +26,74 @@ describe('ReactDOMInvalidARIAHook', () => {
|
||||
|
||||
describe('aria-* props', () => {
|
||||
it('should allow valid aria-* props', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
mountComponent({'aria-label': 'Bumble bees'});
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
it('should warn for one invalid aria-* prop', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
mountComponent({'aria-badprop': 'maybe'});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid aria prop `aria-badprop` on <div> tag. ' +
|
||||
'For details, see https://fb.me/invalid-aria-prop',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid aria prop `aria-badprop` on <div> tag. ' +
|
||||
'For details, see https://fb.me/invalid-aria-prop',
|
||||
);
|
||||
}
|
||||
});
|
||||
it('should warn for many invalid aria-* props', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
mountComponent({
|
||||
'aria-badprop': 'Very tall trees',
|
||||
'aria-malprop': 'Turbulent seas',
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid aria props `aria-badprop`, `aria-malprop` on <div> ' +
|
||||
'tag. For details, see https://fb.me/invalid-aria-prop',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid aria props `aria-badprop`, `aria-malprop` on <div> ' +
|
||||
'tag. For details, see https://fb.me/invalid-aria-prop',
|
||||
);
|
||||
}
|
||||
});
|
||||
it('should warn for an improperly cased aria-* prop', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// The valid attribute name is aria-haspopup.
|
||||
mountComponent({'aria-hasPopup': 'true'});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Unknown ARIA attribute `aria-hasPopup`. ' +
|
||||
'Did you mean `aria-haspopup`?',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Unknown ARIA attribute `aria-hasPopup`. ' +
|
||||
'Did you mean `aria-haspopup`?',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn for use of recognized camel case aria attributes', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// The valid attribute name is aria-haspopup.
|
||||
mountComponent({ariaHasPopup: 'true'});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid ARIA attribute `ariaHasPopup`. ' +
|
||||
'Did you mean `aria-haspopup`?',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid ARIA attribute `ariaHasPopup`. ' +
|
||||
'Did you mean `aria-haspopup`?',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn for use of unrecognized camel case aria attributes', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// The valid attribute name is aria-haspopup.
|
||||
mountComponent({ariaSomethingInvalid: 'true'});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid ARIA attribute `ariaSomethingInvalid`. ARIA ' +
|
||||
'attributes follow the pattern aria-* and must be lowercase.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Invalid ARIA attribute `ariaSomethingInvalid`. ARIA ' +
|
||||
'attributes follow the pattern aria-* and must be lowercase.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+14
-12
@@ -37,7 +37,7 @@ describe('ReactDOMOption', () => {
|
||||
});
|
||||
|
||||
it('should ignore and warn invalid children types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var el = (
|
||||
<option>
|
||||
{1} <div /> {2}
|
||||
@@ -46,15 +46,15 @@ describe('ReactDOMOption', () => {
|
||||
var node = ReactTestUtils.renderIntoDocument(el);
|
||||
expect(node.innerHTML).toBe('1 2');
|
||||
ReactTestUtils.renderIntoDocument(el);
|
||||
// only warn once
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'<div> cannot appear as a child of <option>.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in option (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
// only warn once
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toContain(
|
||||
'<div> cannot appear as a child of <option>.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in option (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should ignore null/undefined/false children without warning', () => {
|
||||
@@ -66,12 +66,14 @@ describe('ReactDOMOption', () => {
|
||||
{undefined} {2}
|
||||
</option>
|
||||
);
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
stub = ReactTestUtils.renderIntoDocument(stub);
|
||||
|
||||
var node = ReactDOM.findDOMNode(stub);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
expect(node.innerHTML).toBe('1 2');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
describe('ReactDOMProduction', () => {
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactDOMServer;
|
||||
var oldProcess;
|
||||
|
||||
beforeEach(() => {
|
||||
__DEV__ = false;
|
||||
|
||||
// Mutating process.env.NODE_ENV would cause our babel plugins to do the
|
||||
// wrong thing. If you change this, make sure to test with jest --no-cache.
|
||||
oldProcess = process;
|
||||
global.process = {
|
||||
...process,
|
||||
env: {...process.env, NODE_ENV: 'production'},
|
||||
};
|
||||
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__DEV__ = true;
|
||||
global.process = oldProcess;
|
||||
});
|
||||
|
||||
it('should use prod fbjs', () => {
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
spyOn(console, 'error');
|
||||
warning(false, 'Do cows go moo?');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('should use prod React', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
// no key warning
|
||||
void <div>{[<span />]}</div>;
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle a simple flow', () => {
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return <span>{this.props.children}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
var inst = ReactDOM.render(
|
||||
<div className="blue" style={{fontFamily: 'Helvetica'}}>
|
||||
<Component key={1}>A</Component>
|
||||
<Component key={2}>B</Component>
|
||||
<Component key={3}>C</Component>
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBe(inst);
|
||||
expect(inst.className).toBe('blue');
|
||||
expect(inst.style.fontFamily).toBe('Helvetica');
|
||||
expect(inst.textContent).toBe('ABC');
|
||||
|
||||
ReactDOM.render(
|
||||
<div className="red" style={{fontFamily: 'Comic Sans MS'}}>
|
||||
<Component key={2}>B</Component>
|
||||
<Component key={1}>A</Component>
|
||||
<Component key={3}>C</Component>
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(inst.className).toBe('red');
|
||||
expect(inst.style.fontFamily).toBe('Comic Sans MS');
|
||||
expect(inst.textContent).toBe('BAC');
|
||||
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should support createFactory', () => {
|
||||
var span = React.createFactory('span');
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return span({children: this.props.children});
|
||||
}
|
||||
}
|
||||
|
||||
var ComponentFactory = React.createFactory(Component);
|
||||
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
ComponentFactory(null, span(null, 'Hello'), span(null, 'world')),
|
||||
container,
|
||||
);
|
||||
expect(container.firstChild.tagName).toBe('SPAN');
|
||||
expect(container.firstChild.childNodes[0].tagName).toBe('SPAN');
|
||||
expect(container.firstChild.childNodes[0].textContent).toBe('Hello');
|
||||
expect(container.firstChild.childNodes[1].tagName).toBe('SPAN');
|
||||
expect(container.firstChild.childNodes[1].textContent).toBe('world');
|
||||
});
|
||||
|
||||
it('should support React public API methods', () => {
|
||||
expect(React.isValidElement(42)).toBe(false);
|
||||
expect(React.isValidElement(<div />)).toBe(true);
|
||||
expect(React.cloneElement(<div />, {foo: 42})).toEqual(<div foo={42} />);
|
||||
|
||||
const mapped = React.Children.map(<div />, el =>
|
||||
React.cloneElement(el, {foo: 42}),
|
||||
);
|
||||
expect(mapped.length).toBe(1);
|
||||
expect(mapped[0].type).toBe('div');
|
||||
expect(mapped[0].props.foo).toBe(42);
|
||||
|
||||
const arr = React.Children.toArray(<div />);
|
||||
expect(arr.length).toBe(1);
|
||||
expect(arr[0].type).toBe('div');
|
||||
|
||||
let called = 0;
|
||||
React.Children.forEach(<div />, () => called++);
|
||||
expect(called).toBe(1);
|
||||
|
||||
expect(React.Children.count(<div />)).toBe(1);
|
||||
expect(() => React.Children.only(42)).toThrowError();
|
||||
});
|
||||
|
||||
it('should handle a simple flow (ssr)', () => {
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return <span>{this.props.children}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
var markup = ReactDOMServer.renderToString(
|
||||
<div className="blue" style={{fontFamily: 'Helvetica'}}>
|
||||
<Component key={1}>A</Component>
|
||||
<Component key={2}>B</Component>
|
||||
<Component key={3}>C</Component>
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
container.innerHTML = markup;
|
||||
var inst = container.firstChild;
|
||||
|
||||
expect(inst.className).toBe('blue');
|
||||
expect(inst.style.fontFamily).toBe('Helvetica');
|
||||
expect(inst.textContent).toBe('ABC');
|
||||
});
|
||||
|
||||
it('should call lifecycle methods', () => {
|
||||
var log = [];
|
||||
class Component extends React.Component {
|
||||
state = {y: 1};
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
log.push(['shouldComponentUpdate', nextProps, nextState]);
|
||||
return nextProps.x !== this.props.x || nextState.y !== this.state.y;
|
||||
}
|
||||
componentWillMount() {
|
||||
log.push(['componentWillMount']);
|
||||
}
|
||||
componentDidMount() {
|
||||
log.push(['componentDidMount']);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
log.push(['componentWillReceiveProps', nextProps]);
|
||||
}
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
log.push(['componentWillUpdate', nextProps, nextState]);
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
log.push(['componentDidUpdate', prevProps, prevState]);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
log.push(['componentWillUnmount']);
|
||||
}
|
||||
render() {
|
||||
log.push(['render']);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
var inst = ReactDOM.render(<Component x={1} />, container);
|
||||
expect(log).toEqual([
|
||||
['componentWillMount'],
|
||||
['render'],
|
||||
['componentDidMount'],
|
||||
]);
|
||||
log = [];
|
||||
|
||||
inst.setState({y: 2});
|
||||
expect(log).toEqual([
|
||||
['shouldComponentUpdate', {x: 1}, {y: 2}],
|
||||
['componentWillUpdate', {x: 1}, {y: 2}],
|
||||
['render'],
|
||||
['componentDidUpdate', {x: 1}, {y: 1}],
|
||||
]);
|
||||
log = [];
|
||||
|
||||
inst.setState({y: 2});
|
||||
expect(log).toEqual([['shouldComponentUpdate', {x: 1}, {y: 2}]]);
|
||||
log = [];
|
||||
|
||||
ReactDOM.render(<Component x={2} />, container);
|
||||
expect(log).toEqual([
|
||||
['componentWillReceiveProps', {x: 2}],
|
||||
['shouldComponentUpdate', {x: 2}, {y: 2}],
|
||||
['componentWillUpdate', {x: 2}, {y: 2}],
|
||||
['render'],
|
||||
['componentDidUpdate', {x: 1}, {y: 2}],
|
||||
]);
|
||||
log = [];
|
||||
|
||||
ReactDOM.render(<Component x={2} />, container);
|
||||
expect(log).toEqual([
|
||||
['componentWillReceiveProps', {x: 2}],
|
||||
['shouldComponentUpdate', {x: 2}, {y: 2}],
|
||||
]);
|
||||
log = [];
|
||||
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
expect(log).toEqual([['componentWillUnmount']]);
|
||||
});
|
||||
|
||||
it('should throw with an error code in production', () => {
|
||||
const errorCode = 152;
|
||||
expect(function() {
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component />, container);
|
||||
}).toThrowError(
|
||||
`Minified React error #${errorCode}; visit ` +
|
||||
`http://facebook.github.io/react/docs/error-decoder.html?invariant=${
|
||||
errorCode
|
||||
}&args[]=Component` +
|
||||
' for the full message or use the non-minified dev environment' +
|
||||
' for full errors and additional helpful warnings.',
|
||||
);
|
||||
});
|
||||
|
||||
// Regression test verifying that trying to access (missing) component stack doesn't crash.
|
||||
it('should throw on children for void elements', () => {
|
||||
const errorCode = 137;
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<input>children</input>, container),
|
||||
).toThrowError(
|
||||
`Minified React error #${errorCode}; visit ` +
|
||||
`http://facebook.github.io/react/docs/error-decoder.html?invariant=${
|
||||
errorCode
|
||||
}&args[]=input&args[]=` +
|
||||
' for the full message or use the non-minified dev environment' +
|
||||
' for full errors and additional helpful warnings.',
|
||||
);
|
||||
expect(() =>
|
||||
ReactDOMServer.renderToString(<input>children</input>, container),
|
||||
).toThrowError(
|
||||
`Minified React error #${errorCode}; visit ` +
|
||||
`http://facebook.github.io/react/docs/error-decoder.html?invariant=${
|
||||
errorCode
|
||||
}&args[]=input&args[]=` +
|
||||
' for the full message or use the non-minified dev environment' +
|
||||
' for full errors and additional helpful warnings.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not crash with devtools installed', () => {
|
||||
try {
|
||||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
inject: function() {},
|
||||
onCommitFiberRoot: function() {},
|
||||
onCommitFiberUnmount: function() {},
|
||||
supportsFiber: true,
|
||||
};
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Component />, document.createElement('container'));
|
||||
} finally {
|
||||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// This test is originally from ReactDOMFiber-test but we replicate it here
|
||||
// to avoid production-only regressions because of host context differences
|
||||
// in dev and prod.
|
||||
it('should keep track of namespace across portals in production', () => {
|
||||
var svgEls, htmlEls;
|
||||
var expectSVG = {ref: el => svgEls.push(el)};
|
||||
var expectHTML = {ref: el => htmlEls.push(el)};
|
||||
var usePortal = function(tree) {
|
||||
return ReactDOM.createPortal(tree, document.createElement('div'));
|
||||
};
|
||||
var assertNamespacesMatch = function(tree) {
|
||||
var container = document.createElement('div');
|
||||
svgEls = [];
|
||||
htmlEls = [];
|
||||
ReactDOM.render(tree, container);
|
||||
svgEls.forEach(el => {
|
||||
expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
||||
});
|
||||
htmlEls.forEach(el => {
|
||||
expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
|
||||
});
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
expect(container.innerHTML).toBe('');
|
||||
};
|
||||
|
||||
assertNamespacesMatch(
|
||||
<div {...expectHTML}>
|
||||
<svg {...expectSVG}>
|
||||
<foreignObject {...expectSVG}>
|
||||
<p {...expectHTML} />
|
||||
{usePortal(
|
||||
<svg {...expectSVG}>
|
||||
<image {...expectSVG} />
|
||||
<svg {...expectSVG}>
|
||||
<image {...expectSVG} />
|
||||
<foreignObject {...expectSVG}>
|
||||
<p {...expectHTML} />
|
||||
</foreignObject>
|
||||
{usePortal(<p {...expectHTML} />)}
|
||||
</svg>
|
||||
<image {...expectSVG} />
|
||||
</svg>,
|
||||
)}
|
||||
<p {...expectHTML} />
|
||||
</foreignObject>
|
||||
<image {...expectSVG} />
|
||||
</svg>
|
||||
<p {...expectHTML} />
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
});
|
||||
+9
-5
@@ -46,7 +46,7 @@ describe('ReactDOMRoot', () => {
|
||||
),
|
||||
);
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
// Does not hydrate by default
|
||||
const container1 = document.createElement('div');
|
||||
@@ -57,7 +57,9 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
// Accepts `hydrate` option
|
||||
const container2 = document.createElement('div');
|
||||
@@ -68,12 +70,14 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toMatch('Extra attributes');
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toMatch('Extra attributes');
|
||||
}
|
||||
});
|
||||
|
||||
it('does not clear existing children', async () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
container.innerHTML = '<div>a</div><div>b</div>';
|
||||
const root = ReactDOM.createRoot(container);
|
||||
root.render(
|
||||
|
||||
+37
-25
@@ -532,49 +532,57 @@ describe('ReactDOMSelect', () => {
|
||||
});
|
||||
|
||||
it('should warn if value is null', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<select value={null}>
|
||||
<option value="test" />
|
||||
</select>,
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `select` should not be null. ' +
|
||||
'Consider using an empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `select` should not be null. ' +
|
||||
'Consider using an empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<select value={null}>
|
||||
<option value="test" />
|
||||
</select>,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if value is null and multiple is true', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<select value={null} multiple={true}>
|
||||
<option value="test" />
|
||||
</select>,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `select` should not be null. ' +
|
||||
'Consider using an empty array when `multiple` is ' +
|
||||
'set to `true` to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `select` should not be null. ' +
|
||||
'Consider using an empty array when `multiple` is ' +
|
||||
'set to `true` to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<select value={null} multiple={true}>
|
||||
<option value="test" />
|
||||
</select>,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should refresh state on change', () => {
|
||||
@@ -594,7 +602,7 @@ describe('ReactDOMSelect', () => {
|
||||
});
|
||||
|
||||
it('should warn if value and defaultValue props are specified', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<select value="giraffe" defaultValue="giraffe" readOnly={true}>
|
||||
<option value="monkey">A monkey!</option>
|
||||
@@ -602,13 +610,15 @@ describe('ReactDOMSelect', () => {
|
||||
<option value="gorilla">A gorilla!</option>
|
||||
</select>,
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Select elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the value prop, or the defaultValue prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled select ' +
|
||||
'element and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Select elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the value prop, or the defaultValue prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled select ' +
|
||||
'element and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<select value="giraffe" defaultValue="giraffe" readOnly={true}>
|
||||
@@ -617,7 +627,9 @@ describe('ReactDOMSelect', () => {
|
||||
<option value="gorilla">A gorilla!</option>
|
||||
</select>,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to safely remove select onChange', () => {
|
||||
|
||||
@@ -42,11 +42,12 @@ async function expectErrors(fn, count) {
|
||||
if (console.error.calls && console.error.calls.reset) {
|
||||
console.error.calls.reset();
|
||||
} else {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
}
|
||||
|
||||
const result = await fn();
|
||||
if (
|
||||
console.error.calls &&
|
||||
console.error.calls.count() !== count &&
|
||||
console.error.calls.count() !== 0
|
||||
) {
|
||||
@@ -62,7 +63,9 @@ async function expectErrors(fn, count) {
|
||||
}
|
||||
}
|
||||
}
|
||||
expectDev(console.error.calls.count()).toBe(count);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(count);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1856,9 +1859,11 @@ describe('ReactDOMServerIntegration', () => {
|
||||
const ObjectComponent = () => ({x: 123});
|
||||
await render(<ObjectComponent />, 1);
|
||||
},
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{x}). If you meant to render a collection of children, use ' +
|
||||
'an array instead.',
|
||||
'Objects are not valid as a React child (found: object with keys {x}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use ' +
|
||||
'an array instead.'
|
||||
: ''),
|
||||
);
|
||||
|
||||
itThrowsWhenRendering(
|
||||
@@ -1871,9 +1876,11 @@ describe('ReactDOMServerIntegration', () => {
|
||||
}
|
||||
await render(<ObjectComponent />, 1);
|
||||
},
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{x}). If you meant to render a collection of children, use ' +
|
||||
'an array instead.',
|
||||
'Objects are not valid as a React child (found: object with keys {x}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use ' +
|
||||
'an array instead.'
|
||||
: ''),
|
||||
);
|
||||
|
||||
itThrowsWhenRendering(
|
||||
@@ -1881,9 +1888,11 @@ describe('ReactDOMServerIntegration', () => {
|
||||
async render => {
|
||||
await render({x: 123});
|
||||
},
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{x}). If you meant to render a collection of children, use ' +
|
||||
'an array instead.',
|
||||
'Objects are not valid as a React child (found: object with keys {x}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use ' +
|
||||
'an array instead.'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -2662,22 +2671,26 @@ describe('ReactDOMServerIntegration', () => {
|
||||
},
|
||||
);
|
||||
|
||||
itThrowsWhenRendering(
|
||||
'if getChildContext exists without childContextTypes',
|
||||
render => {
|
||||
class MyComponent extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
// TODO: this being DEV-only is likely a bug.
|
||||
// https://github.com/facebook/react/issues/11618
|
||||
if (__DEV__) {
|
||||
itThrowsWhenRendering(
|
||||
'if getChildContext exists without childContextTypes',
|
||||
render => {
|
||||
class MyComponent extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
getChildContext() {
|
||||
return {foo: 'bar'};
|
||||
}
|
||||
}
|
||||
getChildContext() {
|
||||
return {foo: 'bar'};
|
||||
}
|
||||
}
|
||||
return render(<MyComponent />);
|
||||
},
|
||||
'MyComponent.getChildContext(): childContextTypes must be defined ' +
|
||||
'in order to use getChildContext().',
|
||||
);
|
||||
return render(<MyComponent />);
|
||||
},
|
||||
'MyComponent.getChildContext(): childContextTypes must be defined ' +
|
||||
'in order to use getChildContext().',
|
||||
);
|
||||
}
|
||||
|
||||
itThrowsWhenRendering(
|
||||
'if getChildContext returns a value not in childContextTypes',
|
||||
|
||||
+48
-28
@@ -207,7 +207,7 @@ describe('ReactDOMTextarea', () => {
|
||||
|
||||
expect(node.value).toBe('0');
|
||||
|
||||
spyOn(console, 'error'); // deprecation warning for `children` content
|
||||
spyOnDev(console, 'error'); // deprecation warning for `children` content
|
||||
|
||||
ReactDOM.render(<textarea>1</textarea>, container);
|
||||
|
||||
@@ -247,13 +247,15 @@ describe('ReactDOMTextarea', () => {
|
||||
});
|
||||
|
||||
it('should treat children like `defaultValue`', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
var stub = <textarea>giraffe</textarea>;
|
||||
var node = renderTextarea(stub, container);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
// Changing children should do nothing, it functions like `defaultValue`.
|
||||
@@ -299,33 +301,39 @@ describe('ReactDOMTextarea', () => {
|
||||
});
|
||||
|
||||
it('should allow numbers as children', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node = renderTextarea(<textarea>{17}</textarea>);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
expect(node.value).toBe('17');
|
||||
});
|
||||
|
||||
it('should allow booleans as children', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node = renderTextarea(<textarea>{false}</textarea>);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
expect(node.value).toBe('false');
|
||||
});
|
||||
|
||||
it('should allow objects as children', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var obj = {
|
||||
toString: function() {
|
||||
return 'sharkswithlasers';
|
||||
},
|
||||
};
|
||||
var node = renderTextarea(<textarea>{obj}</textarea>);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
expect(node.value).toBe('sharkswithlasers');
|
||||
});
|
||||
|
||||
it('should throw with multiple or invalid children', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
@@ -336,7 +344,9 @@ describe('ReactDOMTextarea', () => {
|
||||
);
|
||||
}).toThrow();
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
|
||||
var node;
|
||||
expect(function() {
|
||||
@@ -349,7 +359,9 @@ describe('ReactDOMTextarea', () => {
|
||||
|
||||
expect(node.value).toBe('[object Object]');
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it('should unmount', () => {
|
||||
@@ -359,35 +371,43 @@ describe('ReactDOMTextarea', () => {
|
||||
});
|
||||
|
||||
it('should warn if value is null', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<textarea value={null} />);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `textarea` should not be null. ' +
|
||||
'Consider using an empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'`value` prop on `textarea` should not be null. ' +
|
||||
'Consider using an empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<textarea value={null} />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if value and defaultValue are specified', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea value="foo" defaultValue="bar" readOnly={true} />,
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Textarea elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the value prop, or the defaultValue prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled textarea ' +
|
||||
'and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Textarea elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the value prop, or the defaultValue prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled textarea ' +
|
||||
'and remove one of these props. More info: ' +
|
||||
'https://fb.me/react-controlled-components',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea value="foo" defaultValue="bar" readOnly={true} />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1909,7 +1909,7 @@ describe('ReactErrorBoundaries', () => {
|
||||
});
|
||||
|
||||
it('discards a bad root if the root component fails', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
const X = null;
|
||||
const Y = undefined;
|
||||
|
||||
@@ -30,18 +30,42 @@ describe('ReactMockedComponent', () => {
|
||||
MockedComponent.prototype.render = jest.fn();
|
||||
});
|
||||
|
||||
it('should allow a mocked component to be rendered', () => {
|
||||
it('should allow a mocked component to be rendered in dev', () => {
|
||||
var container = document.createElement('container');
|
||||
ReactDOM.render(<MockedComponent />, container);
|
||||
if (__DEV__) {
|
||||
ReactDOM.render(<MockedComponent />, container);
|
||||
} else {
|
||||
expect(() => ReactDOM.render(<MockedComponent />, container)).toThrow(
|
||||
'Nothing was returned from render.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow a mocked component to be updated', () => {
|
||||
it('should allow a mocked component to be updated in dev', () => {
|
||||
var container = document.createElement('container');
|
||||
ReactDOM.render(<MockedComponent />, container);
|
||||
ReactDOM.render(<MockedComponent />, container);
|
||||
if (__DEV__) {
|
||||
ReactDOM.render(<MockedComponent />, container);
|
||||
} else {
|
||||
expect(() => ReactDOM.render(<MockedComponent />, container)).toThrow(
|
||||
'Nothing was returned from render.',
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
ReactDOM.render(<MockedComponent />, container);
|
||||
} else {
|
||||
expect(() => ReactDOM.render(<MockedComponent />, container)).toThrow(
|
||||
'Nothing was returned from render.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow a mocked component to be rendered (SSR)', () => {
|
||||
ReactDOMServer.renderToString(<MockedComponent />);
|
||||
it('should allow a mocked component to be rendered in dev (SSR)', () => {
|
||||
if (__DEV__) {
|
||||
ReactDOMServer.renderToString(<MockedComponent />);
|
||||
} else {
|
||||
expect(() => ReactDOMServer.renderToString(<MockedComponent />)).toThrow(
|
||||
'Nothing was returned from render.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+63
-45
@@ -55,7 +55,7 @@ describe('ReactMount', () => {
|
||||
});
|
||||
|
||||
it('warns when given a factory', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
@@ -63,12 +63,14 @@ describe('ReactMount', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(Component);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Functions are not valid as a React child. ' +
|
||||
'This may happen if you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Functions are not valid as a React child. ' +
|
||||
'This may happen if you return a Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should render different components in same root', () => {
|
||||
@@ -129,46 +131,54 @@ describe('ReactMount', () => {
|
||||
var container = document.createElement('container');
|
||||
container.innerHTML = ReactDOMServer.renderToString(<div />) + ' ';
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.hydrate(<div />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Did not expect server HTML to contain the text node " " in <container>.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Did not expect server HTML to contain the text node " " in <container>.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if mounting into right padded rendered markup', () => {
|
||||
var container = document.createElement('container');
|
||||
container.innerHTML = ' ' + ReactDOMServer.renderToString(<div />);
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.hydrate(<div />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Did not expect server HTML to contain the text node " " in <container>.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Did not expect server HTML to contain the text node " " in <container>.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not warn if mounting into non-empty node', () => {
|
||||
var container = document.createElement('container');
|
||||
container.innerHTML = '<div></div>';
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.render(<div />, container);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when mounting into document.body', () => {
|
||||
var iFrame = document.createElement('iframe');
|
||||
document.body.appendChild(iFrame);
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactDOM.render(<div />, iFrame.contentDocument.body);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Rendering components directly into document.body is discouraged',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Rendering components directly into document.body is discouraged',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should account for escaping on a checksum mismatch', () => {
|
||||
@@ -178,16 +188,18 @@ describe('ReactMount', () => {
|
||||
);
|
||||
div.innerHTML = markup;
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.hydrate(
|
||||
<div>This markup contains an nbsp entity: client text</div>,
|
||||
div,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Server: "This markup contains an nbsp entity: server text" ' +
|
||||
'Client: "This markup contains an nbsp entity: client text"',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Server: "This markup contains an nbsp entity: server text" ' +
|
||||
'Client: "This markup contains an nbsp entity: client text"',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if render removes React-rendered children', () => {
|
||||
@@ -206,16 +218,18 @@ describe('ReactMount', () => {
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
// Test that blasting away children throws a warning
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var rootNode = container.firstChild;
|
||||
ReactDOM.render(<span />, rootNode);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: render(...): Replacing React-rendered children with a new ' +
|
||||
'root component. If you intended to update the children of this node, ' +
|
||||
'you should instead have the existing children update their state and ' +
|
||||
'render the new components instead of calling ReactDOM.render.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: render(...): Replacing React-rendered children with a new ' +
|
||||
'root component. If you intended to update the children of this node, ' +
|
||||
'you should instead have the existing children update their state and ' +
|
||||
'render the new components instead of calling ReactDOM.render.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if the unmounted node was rendered by another copy of React', () => {
|
||||
@@ -237,17 +251,21 @@ describe('ReactMount', () => {
|
||||
// Make sure ReactDOM and ReactDOMOther are different copies
|
||||
expect(ReactDOM).not.toEqual(ReactDOMOther);
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOMOther.unmountComponentAtNode(container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: unmountComponentAtNode(): The node you're attempting to unmount " +
|
||||
'was rendered by another copy of React.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: unmountComponentAtNode(): The node you're attempting to unmount " +
|
||||
'was rendered by another copy of React.',
|
||||
);
|
||||
}
|
||||
|
||||
// Don't throw a warning if the correct React copy unmounts the node
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('passes the correct callback context', () => {
|
||||
|
||||
+20
-16
@@ -50,15 +50,17 @@ describe('ReactMount', () => {
|
||||
|
||||
// Test that unmounting at a root node gives a helpful warning
|
||||
var rootDiv = mainContainerDiv.firstChild;
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.unmountComponentAtNode(rootDiv);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: unmountComponentAtNode(): The node you're attempting to " +
|
||||
'unmount was rendered by React and is not a top-level container. You ' +
|
||||
'may have accidentally passed in a React root node instead of its ' +
|
||||
'container.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: unmountComponentAtNode(): The node you're attempting to " +
|
||||
'unmount was rendered by React and is not a top-level container. You ' +
|
||||
'may have accidentally passed in a React root node instead of its ' +
|
||||
'container.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when unmounting a non-container, non-root node', () => {
|
||||
@@ -75,14 +77,16 @@ describe('ReactMount', () => {
|
||||
|
||||
// Test that unmounting at a non-root node gives a different warning
|
||||
var nonRootDiv = mainContainerDiv.firstChild.firstChild;
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.unmountComponentAtNode(nonRootDiv);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: unmountComponentAtNode(): The node you're attempting to " +
|
||||
'unmount was rendered by React and is not a top-level container. ' +
|
||||
'Instead, have the parent component update its state and rerender in ' +
|
||||
'order to remove this component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: unmountComponentAtNode(): The node you're attempting to " +
|
||||
'unmount was rendered by React and is not a top-level container. ' +
|
||||
'Instead, have the parent component update its state and rerender in ' +
|
||||
'order to remove this component.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+44
-38
@@ -183,7 +183,7 @@ describe('ReactMultiChild', () => {
|
||||
});
|
||||
|
||||
it('should warn for duplicated array keys with component stack info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
|
||||
@@ -210,24 +210,26 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in WrapperComponent (at **)\n' +
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
' in WrapperComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn for duplicated iterable keys with component stack info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var container = document.createElement('div');
|
||||
|
||||
@@ -275,25 +277,27 @@ describe('ReactMultiChild', () => {
|
||||
container,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in WrapperComponent (at **)\n' +
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]),
|
||||
).toContain(
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
' in WrapperComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn for using maps as children with owner info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return <div>{new Map([['foo', 0], ['bar', 1]])}</div>;
|
||||
@@ -301,18 +305,20 @@ describe('ReactMultiChild', () => {
|
||||
}
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Using Maps as children is unsupported and will likely yield ' +
|
||||
'unexpected results. Convert it to a sequence/iterable of keyed ' +
|
||||
'ReactElements instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Using Maps as children is unsupported and will likely yield ' +
|
||||
'unexpected results. Convert it to a sequence/iterable of keyed ' +
|
||||
'ReactElements instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should reorder bailed-out children', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class LetterInner extends React.Component {
|
||||
render() {
|
||||
|
||||
@@ -73,7 +73,7 @@ var expectChildren = function(container, children) {
|
||||
*/
|
||||
describe('ReactMultiChildText', () => {
|
||||
it('should correctly handle all possible children for render and update', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// prettier-ignore
|
||||
testAllPermutations([
|
||||
// basic values
|
||||
@@ -162,13 +162,15 @@ describe('ReactMultiChildText', () => {
|
||||
['', 'foo', <div>{true}{<div />}{1.2}{''}</div>, 'foo'], ['', 'foo', <div />, 'foo'],
|
||||
]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw if rendering both HTML and children', () => {
|
||||
|
||||
+49
-39
@@ -35,16 +35,18 @@ describe('rendering React components at document', () => {
|
||||
|
||||
describe('with old implicit hydration API', () => {
|
||||
function expectDeprecationWarningWithFiber() {
|
||||
expectDev(console.warn.calls.count()).toBe(1);
|
||||
expectDev(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(1);
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
it('should be able to adopt server markup', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
class Root extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
@@ -73,7 +75,7 @@ describe('rendering React components at document', () => {
|
||||
});
|
||||
|
||||
it('should not be able to unmount component from document node', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
class Root extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
@@ -100,7 +102,7 @@ describe('rendering React components at document', () => {
|
||||
});
|
||||
|
||||
it('should not be able to switch root constructors', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
@@ -141,7 +143,7 @@ describe('rendering React components at document', () => {
|
||||
});
|
||||
|
||||
it('should be able to mount into document', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
@@ -195,20 +197,22 @@ describe('rendering React components at document', () => {
|
||||
);
|
||||
var testDocument = getTestDocument(markup);
|
||||
|
||||
spyOn(console, 'warn');
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'warn');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.render(<Component text="Hello world" />, testDocument);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello world');
|
||||
expectDev(console.warn.calls.count()).toBe(1);
|
||||
expectDev(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Text content did not match.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(1);
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Text content did not match.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw on full document render w/ no markup', () => {
|
||||
@@ -235,7 +239,7 @@ describe('rendering React components at document', () => {
|
||||
});
|
||||
|
||||
it('supports findDOMNode on full-page components', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
var tree = (
|
||||
<html>
|
||||
<head>
|
||||
@@ -371,15 +375,17 @@ describe('rendering React components at document', () => {
|
||||
});
|
||||
|
||||
it('renders over an existing text child without throwing', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
const container = document.createElement('div');
|
||||
container.textContent = 'potato';
|
||||
ReactDOM.hydrate(<div>parsnip</div>, container);
|
||||
expect(container.textContent).toBe('parsnip');
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Expected server HTML to contain a matching <div> in <div>.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Expected server HTML to contain a matching <div> in <div>.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should give helpful errors on state desync', () => {
|
||||
@@ -401,17 +407,19 @@ describe('rendering React components at document', () => {
|
||||
);
|
||||
var testDocument = getTestDocument(markup);
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOM.hydrate(<Component text="Hello world" />, testDocument);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello world');
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Text content did not match.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Text content did not match.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should render w/ no markup to full document', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var testDocument = getTestDocument();
|
||||
|
||||
class Component extends React.Component {
|
||||
@@ -429,11 +437,13 @@ describe('rendering React components at document', () => {
|
||||
|
||||
ReactDOM.hydrate(<Component text="Hello world" />, testDocument);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello world');
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
// getTestDocument() has an extra <meta> that we didn't render.
|
||||
'Did not expect server HTML to contain a <meta> in <head>.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
// getTestDocument() has an extra <meta> that we didn't render.
|
||||
'Did not expect server HTML to contain a <meta> in <head>.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('supports findDOMNode on full-page components', () => {
|
||||
|
||||
+94
-64
@@ -183,8 +183,8 @@ describe('ReactDOMServer', () => {
|
||||
});
|
||||
|
||||
it('should have the correct mounting behavior (old hydrate API)', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'warn');
|
||||
spyOnDev(console, 'error');
|
||||
// This test is testing client-side behavior.
|
||||
ExecutionEnvironment.canUseDOM = true;
|
||||
|
||||
@@ -241,13 +241,15 @@ describe('ReactDOMServer', () => {
|
||||
|
||||
var instance = ReactDOM.render(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(3);
|
||||
expectDev(console.warn.calls.count()).toBe(1);
|
||||
expectDev(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
console.warn.calls.reset();
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(1);
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
);
|
||||
console.warn.calls.reset();
|
||||
}
|
||||
expect(element.innerHTML).toBe(lastMarkup);
|
||||
|
||||
// Ensure the events system works after mount into server markup
|
||||
@@ -263,11 +265,13 @@ describe('ReactDOMServer', () => {
|
||||
element.innerHTML = lastMarkup;
|
||||
instance = ReactDOM.render(<TestComponent name="y" />, element);
|
||||
expect(mountCount).toEqual(4);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Text content did not match. Server: "x" Client: "y"',
|
||||
);
|
||||
console.error.calls.reset();
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Text content did not match. Server: "x" Client: "y"',
|
||||
);
|
||||
console.error.calls.reset();
|
||||
}
|
||||
expect(element.innerHTML.length > 0).toBe(true);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
|
||||
@@ -275,12 +279,14 @@ describe('ReactDOMServer', () => {
|
||||
expect(numClicks).toEqual(1);
|
||||
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
|
||||
expect(numClicks).toEqual(2);
|
||||
expectDev(console.warn.calls.count()).toBe(0);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(0);
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have the correct mounting behavior (new hydrate API)', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
// This test is testing client-side behavior.
|
||||
ExecutionEnvironment.canUseDOM = true;
|
||||
|
||||
@@ -352,7 +358,9 @@ describe('ReactDOMServer', () => {
|
||||
element.innerHTML = lastMarkup;
|
||||
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
|
||||
expect(mountCount).toEqual(4);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
expect(element.innerHTML.length > 0).toBe(true);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
|
||||
@@ -416,7 +424,7 @@ describe('ReactDOMServer', () => {
|
||||
expect(normalizeCodeLocInfo(caughtErr.message)).toContain(
|
||||
'The `style` prop expects a mapping from style properties to values, not ' +
|
||||
"a string. For example, style={{marginRight: spacing + 'em'}} when using JSX." +
|
||||
'\n in iframe (at **)',
|
||||
(__DEV__ ? '\n in iframe (at **)' : ''),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -678,20 +686,24 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
jest.runOnlyPendingTimers();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: setState(...): Can only update a mounting component.' +
|
||||
' This usually means you called setState() outside componentWillMount() on the server.' +
|
||||
' This is a no-op.\n\nPlease check the code for the Foo component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: setState(...): Can only update a mounting component.' +
|
||||
' This usually means you called setState() outside componentWillMount() on the server.' +
|
||||
' This is a no-op.\n\nPlease check the code for the Foo component.',
|
||||
);
|
||||
}
|
||||
|
||||
var markup = ReactDOMServer.renderToStaticMarkup(<Foo />);
|
||||
expect(markup).toBe('<div>hello</div>');
|
||||
jest.runOnlyPendingTimers();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns with a no-op when an async forceUpdate is triggered', () => {
|
||||
@@ -708,38 +720,52 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOMServer.renderToString(<Baz />);
|
||||
jest.runOnlyPendingTimers();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: forceUpdate(...): Can only update a mounting component. ' +
|
||||
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op.\n\nPlease check the code for the Baz component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.mostRecent().args[0]).toBe(
|
||||
'Warning: forceUpdate(...): Can only update a mounting component. ' +
|
||||
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op.\n\nPlease check the code for the Baz component.',
|
||||
);
|
||||
}
|
||||
var markup = ReactDOMServer.renderToStaticMarkup(<Baz />);
|
||||
expect(markup).toBe('<div></div>');
|
||||
});
|
||||
|
||||
it('should warn when children are mutated during render', () => {
|
||||
spyOn(console, 'error');
|
||||
it('should throw (in dev) when children are mutated during render', () => {
|
||||
spyOnDev(console, 'error');
|
||||
function Wrapper(props) {
|
||||
props.children[1] = <p key={1} />; // Mutation is illegal
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToStaticMarkup(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToStaticMarkup(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
} else {
|
||||
expect(
|
||||
ReactDOMServer.renderToStaticMarkup(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
),
|
||||
).toContain('<p>');
|
||||
}
|
||||
});
|
||||
|
||||
it('warns about lowercase html but not in svg tags', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
function CompositeG(props) {
|
||||
// Make sure namespace passes through composites
|
||||
return <g>{props.children}</g>;
|
||||
@@ -758,27 +784,31 @@ describe('ReactDOMServer', () => {
|
||||
</svg>
|
||||
</div>,
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: <inPUT /> is using uppercase HTML. Always use lowercase ' +
|
||||
'HTML tags in React.',
|
||||
);
|
||||
// linearGradient doesn't warn
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: <iFrame /> is using uppercase HTML. Always use lowercase ' +
|
||||
'HTML tags in React.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: <inPUT /> is using uppercase HTML. Always use lowercase ' +
|
||||
'HTML tags in React.',
|
||||
);
|
||||
// linearGradient doesn't warn
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: <iFrame /> is using uppercase HTML. Always use lowercase ' +
|
||||
'HTML tags in React.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn about contentEditable and children', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactDOMServer.renderToString(<div contentEditable={true} children="" />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is `contentEditable` and contains `children` ' +
|
||||
'managed by React. It is now your responsibility to guarantee that ' +
|
||||
'none of those nodes are unexpectedly modified or duplicated. This ' +
|
||||
'is probably not intentional.\n in div (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: A component is `contentEditable` and contains `children` ' +
|
||||
'managed by React. It is now your responsibility to guarantee that ' +
|
||||
'none of those nodes are unexpectedly modified or duplicated. This ' +
|
||||
'is probably not intentional.\n in div (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+118
-79
@@ -103,7 +103,7 @@ describe('ReactStatelessComponent', () => {
|
||||
});
|
||||
|
||||
it('should warn for childContextTypes on a functional component', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
function StatelessComponentWithChildContext(props) {
|
||||
return <div>{props.name}</div>;
|
||||
}
|
||||
@@ -116,11 +116,13 @@ describe('ReactStatelessComponent', () => {
|
||||
|
||||
ReactDOM.render(<StatelessComponentWithChildContext name="A" />, container);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'StatelessComponentWithChildContext(...): childContextTypes cannot ' +
|
||||
'be defined on a functional component.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'StatelessComponentWithChildContext(...): childContextTypes cannot ' +
|
||||
'be defined on a functional component.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw when stateless component returns undefined', () => {
|
||||
@@ -144,11 +146,18 @@ describe('ReactStatelessComponent', () => {
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<Child test="test" />);
|
||||
}).toThrowError('Stateless function components cannot have refs.');
|
||||
}).toThrowError(
|
||||
__DEV__
|
||||
? 'Stateless function components cannot have refs.'
|
||||
: // TODO: the different message in production seems like a bug.
|
||||
// It happens because we don't save _owner in production for
|
||||
// functional components. We should probably show a better message.
|
||||
'Element ref was specified as a string (me) but no owner was set.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when given a string ref', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Indirection(props) {
|
||||
return <div>{props.children}</div>;
|
||||
@@ -165,23 +174,27 @@ describe('ReactStatelessComponent', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `ParentUsingStringRef`.\n' +
|
||||
' in StatelessComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Indirection (at **)\n' +
|
||||
' in ParentUsingStringRef (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `ParentUsingStringRef`.\n' +
|
||||
' in StatelessComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Indirection (at **)\n' +
|
||||
' in ParentUsingStringRef (at **)',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ParentUsingStringRef />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when given a function ref', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Indirection(props) {
|
||||
return <div>{props.children}</div>;
|
||||
@@ -203,23 +216,27 @@ describe('ReactStatelessComponent', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `ParentUsingFunctionRef`.\n' +
|
||||
' in StatelessComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Indirection (at **)\n' +
|
||||
' in ParentUsingFunctionRef (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `ParentUsingFunctionRef`.\n' +
|
||||
' in StatelessComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Indirection (at **)\n' +
|
||||
' in ParentUsingFunctionRef (at **)',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ParentUsingFunctionRef />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('deduplicates ref warnings based on element or owner', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
// When owner uses JSX, we can use exact line location to dedupe warnings
|
||||
class AnonymousParentUsingJSX extends React.Component {
|
||||
@@ -232,16 +249,20 @@ describe('ReactStatelessComponent', () => {
|
||||
const instance1 = ReactTestUtils.renderIntoDocument(
|
||||
<AnonymousParentUsingJSX />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
}
|
||||
// Should be deduped (offending element is on the same line):
|
||||
instance1.forceUpdate();
|
||||
// Should also be deduped (offending element is on the same line):
|
||||
ReactTestUtils.renderIntoDocument(<AnonymousParentUsingJSX />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
console.error.calls.reset();
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
console.error.calls.reset();
|
||||
}
|
||||
|
||||
// When owner doesn't use JSX, and is anonymous, we warn once per internal instance.
|
||||
class AnonymousParentNotUsingJSX extends React.Component {
|
||||
@@ -259,20 +280,26 @@ describe('ReactStatelessComponent', () => {
|
||||
const instance2 = ReactTestUtils.renderIntoDocument(
|
||||
<AnonymousParentNotUsingJSX />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
}
|
||||
// Should be deduped (same internal instance):
|
||||
instance2.forceUpdate();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
// Could not be deduped (different internal instance):
|
||||
ReactTestUtils.renderIntoDocument(<AnonymousParentNotUsingJSX />);
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
console.error.calls.reset();
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
console.error.calls.reset();
|
||||
}
|
||||
|
||||
// When owner doesn't use JSX, but is named, we warn once per owner name
|
||||
class NamedParentNotUsingJSX extends React.Component {
|
||||
@@ -286,23 +313,29 @@ describe('ReactStatelessComponent', () => {
|
||||
const instance3 = ReactTestUtils.renderIntoDocument(
|
||||
<NamedParentNotUsingJSX />,
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Stateless function components cannot be given refs.',
|
||||
);
|
||||
}
|
||||
// Should be deduped (same owner name):
|
||||
instance3.forceUpdate();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
// Should also be deduped (same owner name):
|
||||
ReactTestUtils.renderIntoDocument(<NamedParentNotUsingJSX />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
console.error.calls.reset();
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
console.error.calls.reset();
|
||||
}
|
||||
});
|
||||
|
||||
// This guards against a regression caused by clearing the current debug fiber.
|
||||
// https://github.com/facebook/react/issues/10831
|
||||
it('should warn when giving a function ref with context', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Child() {
|
||||
return null;
|
||||
@@ -326,14 +359,16 @@ describe('ReactStatelessComponent', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Parent />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `Parent`.\n' +
|
||||
' in Child (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `Parent`.\n' +
|
||||
' in Child (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should provide a null ref', () => {
|
||||
@@ -350,13 +385,15 @@ describe('ReactStatelessComponent', () => {
|
||||
return <div>{[<span />]}</div>;
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactTestUtils.renderIntoDocument(<Child />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'a unique "key" prop',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain('Child');
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'a unique "key" prop',
|
||||
);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain('Child');
|
||||
}
|
||||
});
|
||||
|
||||
it('should support default props and prop types', () => {
|
||||
@@ -366,16 +403,18 @@ describe('ReactStatelessComponent', () => {
|
||||
Child.defaultProps = {test: 2};
|
||||
Child.propTypes = {test: PropTypes.string};
|
||||
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
ReactTestUtils.renderIntoDocument(<Child />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
|
||||
).toBe(
|
||||
'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
|
||||
'supplied to `Child`, expected `string`.\n' +
|
||||
' in Child (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
|
||||
).toBe(
|
||||
'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
|
||||
'supplied to `Child`, expected `string`.\n' +
|
||||
' in Child (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should receive context', () => {
|
||||
|
||||
@@ -365,8 +365,6 @@ describe('ReactTestUtils', () => {
|
||||
});
|
||||
|
||||
it('should not warn when used with extra properties', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
const CLIENT_X = 100;
|
||||
|
||||
class Component extends React.Component {
|
||||
@@ -384,7 +382,6 @@ describe('ReactTestUtils', () => {
|
||||
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance), {
|
||||
clientX: CLIENT_X,
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('should set the type of the event', () => {
|
||||
|
||||
+47
-33
@@ -842,7 +842,7 @@ describe('ReactUpdates', () => {
|
||||
});
|
||||
|
||||
it('throws in setState if the update callback is not a function', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Foo() {
|
||||
this.a = 1;
|
||||
@@ -863,33 +863,39 @@ describe('ReactUpdates', () => {
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'setState(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'setState(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
}
|
||||
component = ReactTestUtils.renderIntoDocument(<A />);
|
||||
expect(() => component.setState({}, {foo: 'bar'})).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'setState(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'setState(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
}
|
||||
component = ReactTestUtils.renderIntoDocument(<A />);
|
||||
expect(() => component.setState({}, new Foo())).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'setState(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'setState(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws in forceUpdate if the update callback is not a function', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Foo() {
|
||||
this.a = 1;
|
||||
@@ -910,29 +916,35 @@ describe('ReactUpdates', () => {
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
);
|
||||
}
|
||||
component = ReactTestUtils.renderIntoDocument(<A />);
|
||||
expect(() => component.forceUpdate({foo: 'bar'})).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
}
|
||||
component = ReactTestUtils.renderIntoDocument(<A />);
|
||||
expect(() => component.forceUpdate(new Foo())).toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'forceUpdate(...): Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
);
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not update one component twice in a batch (#2410)', () => {
|
||||
@@ -1216,7 +1228,7 @@ describe('ReactUpdates', () => {
|
||||
);
|
||||
|
||||
it('uses correct base state for setState inside render phase', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
let ops = [];
|
||||
|
||||
@@ -1236,10 +1248,12 @@ describe('ReactUpdates', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Foo />, container);
|
||||
expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Cannot update during an existing state transition',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Cannot update during an existing state transition',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not re-render if state update is null', () => {
|
||||
|
||||
+9
-50
@@ -256,7 +256,7 @@ describe('ref swapping', () => {
|
||||
expect(!!instance.refs).toBe(true);
|
||||
});
|
||||
|
||||
function testRefCall() {
|
||||
it('ref called correctly for stateless component', () => {
|
||||
var refCalled = 0;
|
||||
function Inner(props) {
|
||||
return <a ref={props.saveA} />;
|
||||
@@ -278,20 +278,6 @@ describe('ref swapping', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Outer />);
|
||||
expect(refCalled).toBe(1);
|
||||
}
|
||||
|
||||
it('ref called correctly for stateless component when __DEV__ = false', () => {
|
||||
var originalDev = __DEV__;
|
||||
__DEV__ = false;
|
||||
testRefCall();
|
||||
__DEV__ = originalDev;
|
||||
});
|
||||
|
||||
it('ref called correctly for stateless component when __DEV__ = true', () => {
|
||||
var originalDev = __DEV__;
|
||||
__DEV__ = true;
|
||||
testRefCall();
|
||||
__DEV__ = originalDev;
|
||||
});
|
||||
|
||||
it('coerces numbers to strings', () => {
|
||||
@@ -400,42 +386,15 @@ describe('creating element with ref in constructor', () => {
|
||||
}
|
||||
}
|
||||
|
||||
it('throws an error when __DEV__ = true', () => {
|
||||
it('throws an error', () => {
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
var originalDev = __DEV__;
|
||||
__DEV__ = true;
|
||||
|
||||
try {
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<RefTest />);
|
||||
}).toThrowError(
|
||||
'Element ref was specified as a string (p) but no owner was ' +
|
||||
'set. You may have multiple copies of React loaded. ' +
|
||||
'(details: https://fb.me/react-refs-must-have-owner).',
|
||||
);
|
||||
} finally {
|
||||
__DEV__ = originalDev;
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error when __DEV__ = false', () => {
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
var originalDev = __DEV__;
|
||||
__DEV__ = false;
|
||||
|
||||
try {
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<RefTest />);
|
||||
}).toThrowError(
|
||||
'Minified React error #149; visit ' +
|
||||
'http://facebook.github.io/react/docs/error-decoder.html?invariant=149&args[]=p ' +
|
||||
'for the full message or use the non-minified dev environment for full errors and additional ' +
|
||||
'helpful warnings.',
|
||||
);
|
||||
} finally {
|
||||
__DEV__ = originalDev;
|
||||
}
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<RefTest />);
|
||||
}).toThrowError(
|
||||
'Element ref was specified as a string (p) but no owner was ' +
|
||||
'set. You may have multiple copies of React loaded. ' +
|
||||
'(details: https://fb.me/react-refs-must-have-owner).',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -310,12 +310,14 @@ describe('renderSubtreeIntoContainer', () => {
|
||||
expect(() => {
|
||||
c._reactInternalInstance._processChildContext({});
|
||||
}).toThrow(
|
||||
'_processChildContext is not available in React 16+. This likely ' +
|
||||
'means you have multiple copies of React and are attempting to nest ' +
|
||||
'a React 15 tree inside a React 16 tree using ' +
|
||||
"unstable_renderSubtreeIntoContainer, which isn't supported. Try to " +
|
||||
'make sure you have only one copy of React (and ideally, switch to ' +
|
||||
'ReactDOM.createPortal).',
|
||||
__DEV__
|
||||
? '_processChildContext is not available in React 16+. This likely ' +
|
||||
'means you have multiple copies of React and are attempting to nest ' +
|
||||
'a React 15 tree inside a React 16 tree using ' +
|
||||
"unstable_renderSubtreeIntoContainer, which isn't supported. Try to " +
|
||||
'make sure you have only one copy of React (and ideally, switch to ' +
|
||||
'ReactDOM.createPortal).'
|
||||
: "Cannot read property '_processChildContext' of undefined",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+34
-26
@@ -131,7 +131,7 @@ function isTagStackValid(stack) {
|
||||
return true;
|
||||
}
|
||||
|
||||
describe('ReactContextValidator', () => {
|
||||
describe('validateDOMNesting', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
@@ -140,37 +140,45 @@ describe('ReactContextValidator', () => {
|
||||
});
|
||||
|
||||
it('allows any tag with no context', () => {
|
||||
// With renderToString (for example), we don't know where we're mounting the
|
||||
// tag so we must err on the side of leniency.
|
||||
var allTags = [].concat(specialTags, formattingTags, ['mysterytag']);
|
||||
allTags.forEach(function(tag) {
|
||||
expect(validateDOMNesting.isTagValidInContext(tag, null)).toBe(true);
|
||||
});
|
||||
if (__DEV__) {
|
||||
// With renderToString (for example), we don't know where we're mounting the
|
||||
// tag so we must err on the side of leniency.
|
||||
var allTags = [].concat(specialTags, formattingTags, ['mysterytag']);
|
||||
allTags.forEach(function(tag) {
|
||||
expect(validateDOMNesting.isTagValidInContext(tag, null)).toBe(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('allows valid nestings', () => {
|
||||
expect(isTagStackValid(['table', 'tbody', 'tr', 'td', 'b'])).toBe(true);
|
||||
expect(isTagStackValid(['body', 'datalist', 'option'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'a', 'object', 'a'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'p', 'button', 'p'])).toBe(true);
|
||||
expect(isTagStackValid(['p', 'svg', 'foreignObject', 'p'])).toBe(true);
|
||||
expect(isTagStackValid(['html', 'body', 'div'])).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(isTagStackValid(['table', 'tbody', 'tr', 'td', 'b'])).toBe(true);
|
||||
expect(isTagStackValid(['body', 'datalist', 'option'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'a', 'object', 'a'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'p', 'button', 'p'])).toBe(true);
|
||||
expect(isTagStackValid(['p', 'svg', 'foreignObject', 'p'])).toBe(true);
|
||||
expect(isTagStackValid(['html', 'body', 'div'])).toBe(true);
|
||||
|
||||
// Invalid, but not changed by browser parsing so we allow them
|
||||
expect(isTagStackValid(['div', 'ul', 'ul', 'li'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'label', 'div'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'ul', 'li', 'section', 'li'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'ul', 'li', 'dd', 'li'])).toBe(true);
|
||||
// Invalid, but not changed by browser parsing so we allow them
|
||||
expect(isTagStackValid(['div', 'ul', 'ul', 'li'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'label', 'div'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'ul', 'li', 'section', 'li'])).toBe(true);
|
||||
expect(isTagStackValid(['div', 'ul', 'li', 'dd', 'li'])).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('prevents problematic nestings', () => {
|
||||
expect(isTagStackValid(['a', 'a'])).toBe(false);
|
||||
expect(isTagStackValid(['form', 'form'])).toBe(false);
|
||||
expect(isTagStackValid(['p', 'p'])).toBe(false);
|
||||
expect(isTagStackValid(['table', 'tr'])).toBe(false);
|
||||
expect(isTagStackValid(['div', 'ul', 'li', 'div', 'li'])).toBe(false);
|
||||
expect(isTagStackValid(['div', 'html'])).toBe(false);
|
||||
expect(isTagStackValid(['body', 'body'])).toBe(false);
|
||||
expect(isTagStackValid(['svg', 'foreignObject', 'body', 'p'])).toBe(false);
|
||||
if (__DEV__) {
|
||||
expect(isTagStackValid(['a', 'a'])).toBe(false);
|
||||
expect(isTagStackValid(['form', 'form'])).toBe(false);
|
||||
expect(isTagStackValid(['p', 'p'])).toBe(false);
|
||||
expect(isTagStackValid(['table', 'tr'])).toBe(false);
|
||||
expect(isTagStackValid(['div', 'ul', 'li', 'div', 'li'])).toBe(false);
|
||||
expect(isTagStackValid(['div', 'html'])).toBe(false);
|
||||
expect(isTagStackValid(['body', 'body'])).toBe(false);
|
||||
expect(isTagStackValid(['svg', 'foreignObject', 'body', 'p'])).toBe(
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+73
-59
@@ -157,7 +157,7 @@ describe('SyntheticEvent', () => {
|
||||
});
|
||||
|
||||
it('should be nullified and log warnings if the synthetic event has not been persisted', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node;
|
||||
var expectedCount = 0;
|
||||
var syntheticEvent;
|
||||
@@ -176,21 +176,23 @@ describe('SyntheticEvent', () => {
|
||||
expect(syntheticEvent.type).toBe(null);
|
||||
expect(syntheticEvent.nativeEvent).toBe(null);
|
||||
expect(syntheticEvent.target).toBe(null);
|
||||
// once for each property accessed
|
||||
expectDev(console.error.calls.count()).toBe(3);
|
||||
// assert the first warning for accessing `type`
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the property `type` on a " +
|
||||
'released/nullified synthetic event. This is set to null. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
// once for each property accessed
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
// assert the first warning for accessing `type`
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the property `type` on a " +
|
||||
'released/nullified synthetic event. This is set to null. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
}
|
||||
expect(expectedCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should warn when setting properties of a synthetic event that has not been persisted', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node;
|
||||
var expectedCount = 0;
|
||||
var syntheticEvent;
|
||||
@@ -207,19 +209,21 @@ describe('SyntheticEvent', () => {
|
||||
node.dispatchEvent(event);
|
||||
|
||||
syntheticEvent.type = 'MouseEvent';
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're setting the property `type` on a " +
|
||||
'released/nullified synthetic event. This is effectively a no-op. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're setting the property `type` on a " +
|
||||
'released/nullified synthetic event. This is effectively a no-op. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
}
|
||||
expect(expectedCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should warn when calling `preventDefault` if the synthetic event has not been persisted', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node;
|
||||
var expectedCount = 0;
|
||||
var syntheticEvent;
|
||||
@@ -235,19 +239,21 @@ describe('SyntheticEvent', () => {
|
||||
node.dispatchEvent(event);
|
||||
|
||||
syntheticEvent.preventDefault();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the method `preventDefault` on a " +
|
||||
'released/nullified synthetic event. This is a no-op function. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the method `preventDefault` on a " +
|
||||
'released/nullified synthetic event. This is a no-op function. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
}
|
||||
expect(expectedCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should warn when calling `stopPropagation` if the synthetic event has not been persisted', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node;
|
||||
var expectedCount = 0;
|
||||
var syntheticEvent;
|
||||
@@ -264,14 +270,16 @@ describe('SyntheticEvent', () => {
|
||||
node.dispatchEvent(event);
|
||||
|
||||
syntheticEvent.stopPropagation();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the method `stopPropagation` on a " +
|
||||
'released/nullified synthetic event. This is a no-op function. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the method `stopPropagation` on a " +
|
||||
'released/nullified synthetic event. This is a no-op function. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
}
|
||||
expect(expectedCount).toBe(1);
|
||||
});
|
||||
|
||||
@@ -279,7 +287,7 @@ describe('SyntheticEvent', () => {
|
||||
// using TestUtils.Simulate to avoid spurious warnings that result from the
|
||||
// way we simulate events.
|
||||
xit('should properly log warnings when events simulated with rendered components', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var event;
|
||||
var element = document.createElement('div');
|
||||
function assignEvent(e) {
|
||||
@@ -287,23 +295,27 @@ describe('SyntheticEvent', () => {
|
||||
}
|
||||
var node = ReactDOM.render(<div onClick={assignEvent} />, element);
|
||||
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(node));
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
|
||||
// access a property to cause the warning
|
||||
event.nativeEvent; // eslint-disable-line no-unused-expressions
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the property `nativeEvent` on a " +
|
||||
'released/nullified synthetic event. This is set to null. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're accessing the property `nativeEvent` on a " +
|
||||
'released/nullified synthetic event. This is set to null. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if Proxy is supported and the synthetic event is added a property', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var node;
|
||||
var expectedCount = 0;
|
||||
var syntheticEvent;
|
||||
@@ -321,16 +333,18 @@ describe('SyntheticEvent', () => {
|
||||
node.dispatchEvent(event);
|
||||
|
||||
expect(syntheticEvent.foo).toBe('bar');
|
||||
if (typeof Proxy === 'function') {
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're adding a new property in the synthetic " +
|
||||
'event object. The property is never released. ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
} else {
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
if (typeof Proxy === 'function') {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
"you're seeing this, you're adding a new property in the synthetic " +
|
||||
'event object. The property is never released. ' +
|
||||
'See https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
} else {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
}
|
||||
expect(expectedCount).toBe(1);
|
||||
});
|
||||
|
||||
+15
-11
@@ -674,7 +674,7 @@ describe('ReactFragment', () => {
|
||||
});
|
||||
|
||||
it('should not preserve state when switching to a keyed fragment to an array', function() {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var ops = [];
|
||||
|
||||
class Stateful extends React.Component {
|
||||
@@ -719,14 +719,16 @@ describe('ReactFragment', () => {
|
||||
|
||||
expect(ops).toEqual([]);
|
||||
expect(ReactNoop.getChildren()).toEqual([div(div(), span())]);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should preserve state when it does not change positions', function() {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var ops = [];
|
||||
|
||||
class Stateful extends React.Component {
|
||||
@@ -769,11 +771,13 @@ describe('ReactFragment', () => {
|
||||
|
||||
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(), div()]);
|
||||
expectDev(console.error.calls.count()).toBe(3);
|
||||
for (let errorIndex = 0; errorIndex < 3; ++errorIndex) {
|
||||
expectDev(console.error.calls.argsFor(errorIndex)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
for (let errorIndex = 0; errorIndex < 3; ++errorIndex) {
|
||||
expect(console.error.calls.argsFor(errorIndex)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+124
-53
@@ -719,7 +719,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
});
|
||||
|
||||
it('catches reconciler errors in a boundary during mounting', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
@@ -747,17 +747,24 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span(
|
||||
'Element type is invalid: expected a string (for built-in components) or ' +
|
||||
'a class/function (for composite components) but got: undefined. ' +
|
||||
"You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.' +
|
||||
'\n\nCheck the render method of `BrokenRender`.',
|
||||
'a class/function (for composite components) but got: undefined.' +
|
||||
(__DEV__
|
||||
? " You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.' +
|
||||
'\n\nCheck the render method of `BrokenRender`.'
|
||||
: ''),
|
||||
),
|
||||
]);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: React.createElement: type is invalid -- expected a string',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('catches reconciler errors in a boundary during update', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
@@ -793,31 +800,46 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span(
|
||||
'Element type is invalid: expected a string (for built-in components) or ' +
|
||||
'a class/function (for composite components) but got: undefined. ' +
|
||||
"You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.' +
|
||||
'\n\nCheck the render method of `BrokenRender`.',
|
||||
'a class/function (for composite components) but got: undefined.' +
|
||||
(__DEV__
|
||||
? " You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.' +
|
||||
'\n\nCheck the render method of `BrokenRender`.'
|
||||
: ''),
|
||||
),
|
||||
]);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: React.createElement: type is invalid -- expected a string',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('recovers from uncaught reconciler errors', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
const InvalidType = undefined;
|
||||
ReactNoop.render(<InvalidType />);
|
||||
expect(() => {
|
||||
ReactNoop.flush();
|
||||
}).toThrowError(
|
||||
'Element type is invalid: expected a string (for built-in components) or ' +
|
||||
'a class/function (for composite components) but got: undefined. ' +
|
||||
"You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.',
|
||||
'a class/function (for composite components) but got: undefined.' +
|
||||
(__DEV__
|
||||
? " You likely forgot to export your component from the file it's " +
|
||||
'defined in, or you might have mixed up default and named imports.'
|
||||
: ''),
|
||||
);
|
||||
|
||||
ReactNoop.render(<span prop="hi" />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('hi')]);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: React.createElement: type is invalid -- expected a string',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('unmounts components with uncaught errors', () => {
|
||||
@@ -941,12 +963,33 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// Assert that we're mocking this file by default.
|
||||
// If this ever changes, we'll need to update this test suite anyway.
|
||||
expect(
|
||||
require('../ReactFiberErrorLogger').logCapturedError._isMockFunction,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore the default (we verified it was being mocked in beforeEach).
|
||||
jest.mock('../ReactFiberErrorLogger');
|
||||
});
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
|
||||
}
|
||||
|
||||
it('should log errors that occur during the begin phase', () => {
|
||||
initReactFiberErrorLoggerMock();
|
||||
initReactFiberErrorLoggerMock(false);
|
||||
// Intentionally spy in production too.
|
||||
// TODO: It is confusing how these tests correctly "see" console.error()s
|
||||
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
|
||||
// and thus unmocked the error logger. It is globally mocked in all other
|
||||
// tests. We did this to silence warnings from intentionally caught errors
|
||||
// in tests. But perhaps we should instead make a pass through all tests,
|
||||
// intentionally assert console.error() is always called for uncaught
|
||||
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
|
||||
spyOn(console, 'error');
|
||||
|
||||
class ErrorThrowingComponent extends React.Component {
|
||||
@@ -971,19 +1014,30 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
const errorMessage = console.error.calls.argsFor(0)[0];
|
||||
expect(normalizeCodeLocInfo(errorMessage)).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
|
||||
' in ErrorThrowingComponent (at **)\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(errorMessage).toContain(
|
||||
'Consider adding an error boundary to your tree to customize error handling behavior.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(errorMessage)).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
|
||||
' in ErrorThrowingComponent (at **)\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(errorMessage).toContain(
|
||||
'Consider adding an error boundary to your tree to customize error handling behavior.',
|
||||
);
|
||||
} else {
|
||||
expect(errorMessage.message).toContain('componentWillMount error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should log errors that occur during the commit phase', () => {
|
||||
initReactFiberErrorLoggerMock();
|
||||
initReactFiberErrorLoggerMock(false);
|
||||
// TODO: It is confusing how these tests correctly "see" console.error()s
|
||||
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
|
||||
// and thus unmocked the error logger. It is globally mocked in all other
|
||||
// tests. We did this to silence warnings from intentionally caught errors
|
||||
// in tests. But perhaps we should instead make a pass through all tests,
|
||||
// intentionally assert console.error() is always called for uncaught
|
||||
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
|
||||
spyOn(console, 'error');
|
||||
|
||||
class ErrorThrowingComponent extends React.Component {
|
||||
@@ -1008,19 +1062,29 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
const errorMessage = console.error.calls.argsFor(0)[0];
|
||||
expect(normalizeCodeLocInfo(errorMessage)).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
|
||||
' in ErrorThrowingComponent (at **)\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(errorMessage).toContain(
|
||||
'Consider adding an error boundary to your tree to customize error handling behavior.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(errorMessage)).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:\n' +
|
||||
' in ErrorThrowingComponent (at **)\n' +
|
||||
' in span (at **)\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
expect(errorMessage).toContain(
|
||||
'Consider adding an error boundary to your tree to customize error handling behavior.',
|
||||
);
|
||||
} else {
|
||||
expect(errorMessage.message).toBe('componentDidMount error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should ignore errors thrown in log method to prevent cycle', () => {
|
||||
initReactFiberErrorLoggerMock(true);
|
||||
// Intentionally spy in production too.
|
||||
// Note: we're seeing console.error() even though ReactFiberErrorLogger is
|
||||
// mocked because we're currently testing the console.error() call inside
|
||||
// ReactFiberScheduler (for cycles). It doesn't get affected by mocking.
|
||||
// TODO: mocking is confusing and we should simplify this and remove
|
||||
// special cases.
|
||||
spyOn(console, 'error');
|
||||
|
||||
class ErrorThrowingComponent extends React.Component {
|
||||
@@ -1060,7 +1124,14 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
});
|
||||
|
||||
it('should relay info about error boundary and retry attempts if applicable', () => {
|
||||
initReactFiberErrorLoggerMock();
|
||||
initReactFiberErrorLoggerMock(false);
|
||||
// TODO: It is confusing how these tests correctly "see" console.error()s
|
||||
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
|
||||
// and thus unmocked the error logger. It is globally mocked in all other
|
||||
// tests. We did this to silence warnings from intentionally caught errors
|
||||
// in tests. But perhaps we should instead make a pass through all tests,
|
||||
// intentionally assert console.error() is always called for uncaught
|
||||
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
|
||||
spyOn(console, 'error');
|
||||
|
||||
class ParentComponent extends React.Component {
|
||||
@@ -1100,26 +1171,26 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
expect(renderAttempts).toBe(2);
|
||||
expect(handleErrorCalls.length).toBe(1);
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:',
|
||||
);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'React will try to recreate this component tree from scratch ' +
|
||||
'using the error boundary you provided, ErrorBoundaryComponent.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'This error was initially handled by the error boundary ErrorBoundaryComponent.\n' +
|
||||
'Recreating the tree from scratch failed so React will unmount the tree.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:',
|
||||
);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'React will try to recreate this component tree from scratch ' +
|
||||
'using the error boundary you provided, ErrorBoundaryComponent.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'The above error occurred in the <ErrorThrowingComponent> component:',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'This error was initially handled by the error boundary ErrorBoundaryComponent.\n' +
|
||||
'Recreating the tree from scratch failed so React will unmount the tree.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('resets instance variables before unmounting failed node', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
componentDidCatch(error) {
|
||||
|
||||
@@ -115,6 +115,8 @@ describe('ReactDebugFiberPerf', () => {
|
||||
resetFlamechart();
|
||||
global.performance = createUserTimingPolyfill();
|
||||
|
||||
require('shared/ReactFeatureFlags').enableUserTimingAPI = true;
|
||||
|
||||
// Import after the polyfill is set up:
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
|
||||
+11
-9
@@ -986,7 +986,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
});
|
||||
|
||||
it('invokes ref callbacks after insertion/update/unmount', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var classInstance = null;
|
||||
|
||||
var ops = [];
|
||||
@@ -1044,14 +1044,16 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
null,
|
||||
]);
|
||||
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `Foo`.\n' +
|
||||
' in FunctionalComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. ' +
|
||||
'Attempts to access this ref will fail.\n\nCheck the render method ' +
|
||||
'of `Foo`.\n' +
|
||||
' in FunctionalComponent (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
|
||||
|
||||
@@ -320,7 +320,7 @@ describe('ReactIncrementalUpdates', () => {
|
||||
});
|
||||
|
||||
it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
let instance;
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
@@ -352,13 +352,15 @@ describe('ReactIncrementalUpdates', () => {
|
||||
]);
|
||||
expect(instance.state).toEqual({a: 'a', b: 'b'});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
|
||||
'from inside an update function. Update functions should be pure, ' +
|
||||
'with zero side-effects. Consider using componentDidUpdate or a ' +
|
||||
'callback.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
|
||||
'from inside an update function. Update functions should be pure, ' +
|
||||
'with zero side-effects. Consider using componentDidUpdate or a ' +
|
||||
'callback.',
|
||||
);
|
||||
}
|
||||
|
||||
// Test deduplication
|
||||
instance.setState(function a() {
|
||||
@@ -366,6 +368,8 @@ describe('ReactIncrementalUpdates', () => {
|
||||
return {b: 'b'};
|
||||
});
|
||||
ReactNoop.flush();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -770,7 +770,7 @@ describe('ReactShallowRenderer', () => {
|
||||
});
|
||||
|
||||
it('can fail context when shallowly rendering', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class SimpleComponent extends React.Component {
|
||||
static contextTypes = {
|
||||
@@ -784,18 +784,20 @@ describe('ReactShallowRenderer', () => {
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
shallowRenderer.render(<SimpleComponent />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
|
||||
).toBe(
|
||||
'Warning: Failed context type: The context `name` is marked as ' +
|
||||
'required in `SimpleComponent`, but its value is `undefined`.\n' +
|
||||
' in SimpleComponent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
|
||||
).toBe(
|
||||
'Warning: Failed context type: The context `name` is marked as ' +
|
||||
'required in `SimpleComponent`, but its value is `undefined`.\n' +
|
||||
' in SimpleComponent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn about propTypes (but only once)', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class SimpleComponent extends React.Component {
|
||||
render() {
|
||||
@@ -810,14 +812,16 @@ describe('ReactShallowRenderer', () => {
|
||||
const shallowRenderer = createRenderer();
|
||||
shallowRenderer.render(React.createElement(SimpleComponent, {name: 123}));
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
|
||||
).toBe(
|
||||
'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
|
||||
'supplied to `SimpleComponent`, expected `string`.\n' +
|
||||
' in SimpleComponent',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'),
|
||||
).toBe(
|
||||
'Warning: Failed prop type: Invalid prop `name` of type `number` ' +
|
||||
'supplied to `SimpleComponent`, expected `string`.\n' +
|
||||
' in SimpleComponent',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should enable rendering of cloned element', () => {
|
||||
@@ -846,7 +850,7 @@ describe('ReactShallowRenderer', () => {
|
||||
});
|
||||
|
||||
it('throws usefully when rendering badly-typed elements', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
const shallowRenderer = createRenderer();
|
||||
|
||||
var Undef = undefined;
|
||||
@@ -873,7 +877,9 @@ describe('ReactShallowRenderer', () => {
|
||||
'components, but the provided element type was `object`.',
|
||||
);
|
||||
|
||||
// One warning for each element creation
|
||||
expectDev(console.error.calls.count()).toBe(4);
|
||||
if (__DEV__) {
|
||||
// One warning for each element creation
|
||||
expect(console.error.calls.count()).toBe(4);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -263,7 +263,7 @@ describe('ReactTestRenderer', () => {
|
||||
});
|
||||
|
||||
it('warns correctly for refs on SFCs', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
function Bar() {
|
||||
return <div>Hello, world</div>;
|
||||
}
|
||||
@@ -279,13 +279,15 @@ describe('ReactTestRenderer', () => {
|
||||
}
|
||||
ReactTestRenderer.create(<Baz />);
|
||||
ReactTestRenderer.create(<Foo />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. Attempts ' +
|
||||
'to access this ref will fail.\n\nCheck the render method of `Foo`.\n' +
|
||||
' in Bar (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Stateless function components cannot be given refs. Attempts ' +
|
||||
'to access this ref will fail.\n\nCheck the render method of `Foo`.\n' +
|
||||
' in Bar (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('allows an optional createNodeMock function', () => {
|
||||
|
||||
@@ -350,7 +350,7 @@ describe('ReactChildren', () => {
|
||||
});
|
||||
|
||||
it('should be called for each child in an iterable without keys', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var threeDivIterable = {
|
||||
'@@iterator': function() {
|
||||
var i = 0;
|
||||
@@ -384,11 +384,13 @@ describe('ReactChildren', () => {
|
||||
|
||||
React.Children.forEach(instance.props.children, callback, context);
|
||||
assertCalls();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
console.error.calls.reset();
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Warning: Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
console.error.calls.reset();
|
||||
}
|
||||
|
||||
var mappedChildren = React.Children.map(
|
||||
instance.props.children,
|
||||
@@ -396,7 +398,9 @@ describe('ReactChildren', () => {
|
||||
context,
|
||||
);
|
||||
assertCalls();
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
expect(mappedChildren).toEqual([
|
||||
<div key=".0" />,
|
||||
<div key=".1" />,
|
||||
@@ -918,8 +922,11 @@ describe('ReactChildren', () => {
|
||||
React.Children.forEach({a: 1, b: 2}, function() {}, null);
|
||||
}).toThrowError(
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{a, b}). If you meant to render a collection of children, use an ' +
|
||||
'array instead.',
|
||||
'{a, b}).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use an ' +
|
||||
'array instead.'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -929,14 +936,17 @@ describe('ReactChildren', () => {
|
||||
expect(function() {
|
||||
React.Children.forEach(/abc/, function() {}, null);
|
||||
}).toThrowError(
|
||||
'Objects are not valid as a React child (found: /abc/). If you meant ' +
|
||||
'to render a collection of children, use an array instead.',
|
||||
'Objects are not valid as a React child (found: /abc/).' +
|
||||
(__DEV__
|
||||
? ' If you meant to render a collection of children, use an ' +
|
||||
'array instead.'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
describe('with fragments enabled', () => {
|
||||
it('warns for keys for arrays of elements in a fragment', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class ComponentReturningArray extends React.Component {
|
||||
render() {
|
||||
return [<div />, <div />];
|
||||
@@ -945,17 +955,19 @@ describe('ReactChildren', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ComponentReturningArray />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: ' +
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
' See https://fb.me/react-warning-keys for more information.' +
|
||||
'\n in ComponentReturningArray (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: ' +
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
' See https://fb.me/react-warning-keys for more information.' +
|
||||
'\n in ComponentReturningArray (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn when there are keys on elements in a fragment', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class ComponentReturningArray extends React.Component {
|
||||
render() {
|
||||
return [<div key="foo" />, <div key="bar" />];
|
||||
@@ -964,20 +976,24 @@ describe('ReactChildren', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ComponentReturningArray />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for keys for arrays at the top level', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument([<div />, <div />]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: ' +
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
' See https://fb.me/react-warning-keys for more information.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: ' +
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
' See https://fb.me/react-warning-keys for more information.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
'use strict';
|
||||
|
||||
var spawnSync = require('child_process').spawnSync;
|
||||
var path = require('path');
|
||||
|
||||
describe('ReactClassEquivalence', () => {
|
||||
it('tests the same thing for es6 classes and CoffeeScript', () => {
|
||||
@@ -28,9 +27,7 @@ describe('ReactClassEquivalence', () => {
|
||||
|
||||
function runJest(testFile) {
|
||||
var cwd = process.cwd();
|
||||
var extension = process.platform === 'win32' ? '.cmd' : '';
|
||||
var jestBin = path.resolve('node_modules', '.bin', 'jest' + extension);
|
||||
var result = spawnSync(jestBin, [testFile], {
|
||||
var result = spawnSync('yarn', ['test', testFile], {
|
||||
cwd,
|
||||
env: Object.assign({}, process.env, {
|
||||
REACT_CLASS_EQUIVALENCE_TEST: 'true',
|
||||
|
||||
@@ -46,13 +46,14 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
expect(Foo.name).toBe 'Foo'
|
||||
|
||||
it 'throws if no render function is defined', ->
|
||||
spyOn console, 'error'
|
||||
spyOnDev console, 'error'
|
||||
class Foo extends React.Component
|
||||
expect(->
|
||||
ReactDOM.render React.createElement(Foo), container
|
||||
).toThrow()
|
||||
expect(console.error.calls.count()).toBe(1)
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain('No `render` method found on the returned component instance')
|
||||
if __DEV__
|
||||
expect(console.error.calls.count()).toBe(1)
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain('No `render` method found on the returned component instance')
|
||||
undefined
|
||||
|
||||
it 'renders a simple stateless component with prop', ->
|
||||
@@ -157,11 +158,20 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
render: ->
|
||||
span()
|
||||
|
||||
expect(->
|
||||
test React.createElement(Foo), 'span', ''
|
||||
).toThrowError(
|
||||
'Foo.state: must be set to an object or null'
|
||||
)
|
||||
if __DEV__
|
||||
expect(->
|
||||
test React.createElement(Foo), 'SPAN', ''
|
||||
).toThrowError(
|
||||
'Foo.state: must be set to an object or null'
|
||||
)
|
||||
else
|
||||
# This is a difference between development and production.
|
||||
# I'm not sure if this is intentional, as generally we avoid this.
|
||||
# TODO: investigate if this was intentional or an oversight.
|
||||
# https://github.com/facebook/react/issues/11618
|
||||
expect(->
|
||||
test React.createElement(Foo), 'SPAN', ''
|
||||
).not.toThrowError()
|
||||
undefined
|
||||
|
||||
it 'should render with null in the initial state property', ->
|
||||
@@ -281,7 +291,7 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
|
||||
it 'warns when classic properties are defined on the instance,
|
||||
but does not invoke them.', ->
|
||||
spyOn console, 'error'
|
||||
spyOnDev console, 'error'
|
||||
getInitialStateWasCalled = false
|
||||
getDefaultPropsWasCalled = false
|
||||
class Foo extends React.Component
|
||||
@@ -304,24 +314,25 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
test React.createElement(Foo), 'SPAN', 'foo'
|
||||
expect(getInitialStateWasCalled).toBe false
|
||||
expect(getDefaultPropsWasCalled).toBe false
|
||||
expect(console.error.calls.count()).toBe 4
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'getInitialState was defined on Foo, a plain JavaScript class.'
|
||||
)
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'getDefaultProps was defined on Foo, a plain JavaScript class.'
|
||||
)
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'propTypes was defined as an instance property on Foo.'
|
||||
)
|
||||
expect(console.error.calls.argsFor(3)[0]).toContain(
|
||||
'contextTypes was defined as an instance property on Foo.'
|
||||
)
|
||||
if __DEV__
|
||||
expect(console.error.calls.count()).toBe 4
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'getInitialState was defined on Foo, a plain JavaScript class.'
|
||||
)
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'getDefaultProps was defined on Foo, a plain JavaScript class.'
|
||||
)
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'propTypes was defined as an instance property on Foo.'
|
||||
)
|
||||
expect(console.error.calls.argsFor(3)[0]).toContain(
|
||||
'contextTypes was defined as an instance property on Foo.'
|
||||
)
|
||||
undefined
|
||||
|
||||
it 'does not warn about getInitialState() on class components
|
||||
if state is also defined.', ->
|
||||
spyOn console, 'error'
|
||||
spyOnDev console, 'error'
|
||||
class Foo extends React.Component
|
||||
constructor: (props) ->
|
||||
super props
|
||||
@@ -335,11 +346,12 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
className: 'foo'
|
||||
|
||||
test React.createElement(Foo), 'SPAN', 'foo'
|
||||
expect(console.error.calls.count()).toBe 0
|
||||
if __DEV__
|
||||
expect(console.error.calls.count()).toBe 0
|
||||
undefined
|
||||
|
||||
it 'should warn when misspelling shouldComponentUpdate', ->
|
||||
spyOn console, 'error'
|
||||
spyOnDev console, 'error'
|
||||
class NamedComponent extends React.Component
|
||||
componentShouldUpdate: ->
|
||||
false
|
||||
@@ -349,16 +361,17 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
className: 'foo'
|
||||
|
||||
test React.createElement(NamedComponent), 'SPAN', 'foo'
|
||||
expect(console.error.calls.count()).toBe 1
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: NamedComponent has a method called componentShouldUpdate().
|
||||
Did you mean shouldComponentUpdate()? The name is phrased as a
|
||||
question because the function is expected to return a value.'
|
||||
)
|
||||
if __DEV__
|
||||
expect(console.error.calls.count()).toBe 1
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: NamedComponent has a method called componentShouldUpdate().
|
||||
Did you mean shouldComponentUpdate()? The name is phrased as a
|
||||
question because the function is expected to return a value.'
|
||||
)
|
||||
undefined
|
||||
|
||||
it 'should warn when misspelling componentWillReceiveProps', ->
|
||||
spyOn console, 'error'
|
||||
spyOnDev console, 'error'
|
||||
class NamedComponent extends React.Component
|
||||
componentWillRecieveProps: ->
|
||||
false
|
||||
@@ -368,26 +381,28 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
className: 'foo'
|
||||
|
||||
test React.createElement(NamedComponent), 'SPAN', 'foo'
|
||||
expect(console.error.calls.count()).toBe 1
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: NamedComponent has a method called componentWillRecieveProps().
|
||||
Did you mean componentWillReceiveProps()?'
|
||||
)
|
||||
if __DEV__
|
||||
expect(console.error.calls.count()).toBe 1
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: NamedComponent has a method called componentWillRecieveProps().
|
||||
Did you mean componentWillReceiveProps()?'
|
||||
)
|
||||
undefined
|
||||
|
||||
it 'should throw AND warn when trying to access classic APIs', ->
|
||||
spyOn console, 'warn'
|
||||
spyOnDev console, 'warn'
|
||||
instance =
|
||||
test Inner(name: 'foo'), 'DIV', 'foo'
|
||||
expect(-> instance.replaceState {}).toThrow()
|
||||
expect(-> instance.isMounted()).toThrow()
|
||||
expect(console.warn.calls.count()).toBe 2
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
expect(console.warn.calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
if __DEV__
|
||||
expect(console.warn.calls.count()).toBe 2
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
expect(console.warn.calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes'
|
||||
)
|
||||
undefined
|
||||
|
||||
it 'supports this.context passed via getChildContext', ->
|
||||
|
||||
@@ -161,7 +161,7 @@ describe('ReactContextValidator', () => {
|
||||
});
|
||||
|
||||
it('should check context types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
@@ -174,13 +174,15 @@ describe('ReactContextValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed context type: ' +
|
||||
'The context `foo` is marked as required in `Component`, but its value ' +
|
||||
'is `undefined`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed context type: ' +
|
||||
'The context `foo` is marked as required in `Component`, but its value ' +
|
||||
'is `undefined`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
}
|
||||
|
||||
class ComponentInFooStringContext extends React.Component {
|
||||
getChildContext() {
|
||||
@@ -202,7 +204,9 @@ describe('ReactContextValidator', () => {
|
||||
);
|
||||
|
||||
// Previous call should not error
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
|
||||
class ComponentInFooNumberContext extends React.Component {
|
||||
getChildContext() {
|
||||
@@ -223,18 +227,20 @@ describe('ReactContextValidator', () => {
|
||||
<ComponentInFooNumberContext fooValue={123} />,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Failed context type: ' +
|
||||
'Invalid context `foo` of type `number` supplied ' +
|
||||
'to `Component`, expected `string`.\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in ComponentInFooNumberContext (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Failed context type: ' +
|
||||
'Invalid context `foo` of type `number` supplied ' +
|
||||
'to `Component`, expected `string`.\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in ComponentInFooNumberContext (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should check child context types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
getChildContext() {
|
||||
@@ -251,23 +257,27 @@ describe('ReactContextValidator', () => {
|
||||
};
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed child context type: ' +
|
||||
'The child context `foo` is marked as required in `Component`, but its ' +
|
||||
'value is `undefined`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed child context type: ' +
|
||||
'The child context `foo` is marked as required in `Component`, but its ' +
|
||||
'value is `undefined`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Failed child context type: ' +
|
||||
'Invalid child context `foo` of type `number` ' +
|
||||
'supplied to `Component`, expected `string`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Failed child context type: ' +
|
||||
'Invalid child context `foo` of type `number` ' +
|
||||
'supplied to `Component`, expected `string`.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 'foo', bar: 123}} />,
|
||||
@@ -275,14 +285,16 @@ describe('ReactContextValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 'foo'}} />);
|
||||
|
||||
// Previous calls should not log errors
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
if (__DEV__) {
|
||||
// Previous calls should not log errors
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO (bvaughn) Remove this test and the associated behavior in the future.
|
||||
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
|
||||
it('should warn (but not error) if getChildContext method is missing', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class ComponentA extends React.Component {
|
||||
static childContextTypes = {
|
||||
@@ -302,29 +314,35 @@ describe('ReactContextValidator', () => {
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ComponentA />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: ComponentA.childContextTypes is specified but there is no ' +
|
||||
'getChildContext() method on the instance. You can either define ' +
|
||||
'getChildContext() on ComponentA or remove childContextTypes from it.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: ComponentA.childContextTypes is specified but there is no ' +
|
||||
'getChildContext() method on the instance. You can either define ' +
|
||||
'getChildContext() on ComponentA or remove childContextTypes from it.',
|
||||
);
|
||||
}
|
||||
|
||||
// Warnings should be deduped by component type
|
||||
ReactTestUtils.renderIntoDocument(<ComponentA />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
}
|
||||
ReactTestUtils.renderIntoDocument(<ComponentB />);
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: ComponentB.childContextTypes is specified but there is no ' +
|
||||
'getChildContext() method on the instance. You can either define ' +
|
||||
'getChildContext() on ComponentB or remove childContextTypes from it.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: ComponentB.childContextTypes is specified but there is no ' +
|
||||
'getChildContext() method on the instance. You can either define ' +
|
||||
'getChildContext() on ComponentB or remove childContextTypes from it.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO (bvaughn) Remove this test and the associated behavior in the future.
|
||||
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
|
||||
it('should pass parent context if getChildContext method is missing', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class ParentContextProvider extends React.Component {
|
||||
static childContextTypes = {
|
||||
|
||||
@@ -56,15 +56,17 @@ describe('ReactES6Class', () => {
|
||||
});
|
||||
|
||||
it('throws if no render function is defined', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Foo extends React.Component {}
|
||||
expect(() => ReactDOM.render(<Foo />, container)).toThrow();
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Foo(...): No `render` method found on the returned component ' +
|
||||
'instance: you may have forgotten to define `render`.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Foo(...): No `render` method found on the returned component ' +
|
||||
'instance: you may have forgotten to define `render`.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('renders a simple stateless component with prop', () => {
|
||||
@@ -172,9 +174,17 @@ describe('ReactES6Class', () => {
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
expect(() => test(<Foo />, 'span', '')).toThrowError(
|
||||
'Foo.state: must be set to an object or null',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(() => test(<Foo />, 'SPAN', '')).toThrowError(
|
||||
'Foo.state: must be set to an object or null',
|
||||
);
|
||||
} else {
|
||||
// This is a difference between development and production.
|
||||
// I'm not sure if this is intentional, as generally we avoid this.
|
||||
// TODO: investigate if this was intentional or an oversight.
|
||||
// https://github.com/facebook/react/issues/11618
|
||||
expect(() => test(<Foo />, 'SPAN', '')).not.toThrowError();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -302,7 +312,7 @@ describe('ReactES6Class', () => {
|
||||
});
|
||||
|
||||
it('warns when classic properties are defined on the instance, but does not invoke them.', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var getDefaultPropsWasCalled = false;
|
||||
var getInitialStateWasCalled = false;
|
||||
class Foo extends React.Component {
|
||||
@@ -326,23 +336,25 @@ describe('ReactES6Class', () => {
|
||||
test(<Foo />, 'SPAN', 'foo');
|
||||
expect(getInitialStateWasCalled).toBe(false);
|
||||
expect(getDefaultPropsWasCalled).toBe(false);
|
||||
expect(console.error.calls.count()).toBe(4);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'getInitialState was defined on Foo, a plain JavaScript class.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'getDefaultProps was defined on Foo, a plain JavaScript class.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'propTypes was defined as an instance property on Foo.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(3)[0]).toContain(
|
||||
'contextTypes was defined as an instance property on Foo.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(4);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'getInitialState was defined on Foo, a plain JavaScript class.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toContain(
|
||||
'getDefaultProps was defined on Foo, a plain JavaScript class.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(2)[0]).toContain(
|
||||
'propTypes was defined as an instance property on Foo.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(3)[0]).toContain(
|
||||
'contextTypes was defined as an instance property on Foo.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn about getInitialState() on class components if state is also defined.', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Foo extends React.Component {
|
||||
state = this.getInitialState();
|
||||
getInitialState() {
|
||||
@@ -353,11 +365,13 @@ describe('ReactES6Class', () => {
|
||||
}
|
||||
}
|
||||
test(<Foo />, 'SPAN', 'foo');
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when misspelling shouldComponentUpdate', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class NamedComponent extends React.Component {
|
||||
componentShouldUpdate() {
|
||||
@@ -369,17 +383,19 @@ describe('ReactES6Class', () => {
|
||||
}
|
||||
test(<NamedComponent />, 'SPAN', 'foo');
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'NamedComponent has a method called componentShouldUpdate(). Did you ' +
|
||||
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'NamedComponent has a method called componentShouldUpdate(). Did you ' +
|
||||
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when misspelling componentWillReceiveProps', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class NamedComponent extends React.Component {
|
||||
componentWillRecieveProps() {
|
||||
@@ -391,26 +407,30 @@ describe('ReactES6Class', () => {
|
||||
}
|
||||
test(<NamedComponent />, 'SPAN', 'foo');
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'NamedComponent has a method called componentWillRecieveProps(). Did ' +
|
||||
'you mean componentWillReceiveProps()?',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'NamedComponent has a method called componentWillRecieveProps(). Did ' +
|
||||
'you mean componentWillReceiveProps()?',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw AND warn when trying to access classic APIs', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
var instance = test(<Inner name="foo" />, 'DIV', 'foo');
|
||||
expect(() => instance.replaceState({})).toThrow();
|
||||
expect(() => instance.isMounted()).toThrow();
|
||||
expect(console.warn.calls.count()).toBe(2);
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
expect(console.warn.calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(2);
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
expect(console.warn.calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('supports this.context passed via getChildContext', () => {
|
||||
|
||||
@@ -50,13 +50,15 @@ describe('ReactElement', () => {
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe(null);
|
||||
expect(element.ref).toBe(null);
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(element.props).toEqual({});
|
||||
});
|
||||
|
||||
it('should warn when `key` is being accessed on composite element', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
class Child extends React.Component {
|
||||
render() {
|
||||
@@ -74,33 +76,41 @@ describe('ReactElement', () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Child: `key` is not a prop. Trying to access it will result ' +
|
||||
'in `undefined` being returned. If you need to access the same ' +
|
||||
'value within the child component, you should pass it as a different ' +
|
||||
'prop. (https://fb.me/react-special-props)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Child: `key` is not a prop. Trying to access it will result ' +
|
||||
'in `undefined` being returned. If you need to access the same ' +
|
||||
'value within the child component, you should pass it as a different ' +
|
||||
'prop. (https://fb.me/react-special-props)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when `key` is being accessed on a host element', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var element = <div key="3" />;
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
void element.props.key;
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'div: `key` is not a prop. Trying to access it will result ' +
|
||||
'in `undefined` being returned. If you need to access the same ' +
|
||||
'value within the child component, you should pass it as a different ' +
|
||||
'prop. (https://fb.me/react-special-props)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'div: `key` is not a prop. Trying to access it will result ' +
|
||||
'in `undefined` being returned. If you need to access the same ' +
|
||||
'value within the child component, you should pass it as a different ' +
|
||||
'prop. (https://fb.me/react-special-props)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when `ref` is being accessed', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var container = document.createElement('div');
|
||||
class Child extends React.Component {
|
||||
render() {
|
||||
@@ -116,15 +126,19 @@ describe('ReactElement', () => {
|
||||
);
|
||||
}
|
||||
}
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Child: `ref` is not a prop. Trying to access it will result ' +
|
||||
'in `undefined` being returned. If you need to access the same ' +
|
||||
'value within the child component, you should pass it as a different ' +
|
||||
'prop. (https://fb.me/react-special-props)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Child: `ref` is not a prop. Trying to access it will result ' +
|
||||
'in `undefined` being returned. If you need to access the same ' +
|
||||
'value within the child component, you should pass it as a different ' +
|
||||
'prop. (https://fb.me/react-special-props)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('allows a string to be passed as the type', () => {
|
||||
@@ -132,14 +146,20 @@ describe('ReactElement', () => {
|
||||
expect(element.type).toBe('div');
|
||||
expect(element.key).toBe(null);
|
||||
expect(element.ref).toBe(null);
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(element.props).toEqual({});
|
||||
});
|
||||
|
||||
it('returns an immutable element', () => {
|
||||
var element = React.createFactory(ComponentClass)();
|
||||
expect(() => (element.type = 'div')).toThrow();
|
||||
if (__DEV__) {
|
||||
expect(() => (element.type = 'div')).toThrow();
|
||||
} else {
|
||||
expect(() => (element.type = 'div')).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not reuse the original config object', () => {
|
||||
@@ -165,8 +185,10 @@ describe('ReactElement', () => {
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe('12');
|
||||
expect(element.ref).toBe('34');
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(element.props).toEqual({foo: '56'});
|
||||
});
|
||||
|
||||
@@ -179,8 +201,10 @@ describe('ReactElement', () => {
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe('null');
|
||||
expect(element.ref).toBe(null);
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(element.props).toEqual({foo: '12'});
|
||||
});
|
||||
|
||||
@@ -194,8 +218,10 @@ describe('ReactElement', () => {
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe(null);
|
||||
expect(element.ref).toBe(null);
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(element.props).toEqual({foo: '56'});
|
||||
});
|
||||
|
||||
@@ -214,8 +240,10 @@ describe('ReactElement', () => {
|
||||
expect(element.type).toBe(ComponentClass);
|
||||
expect(element.key).toBe('12');
|
||||
expect(element.ref).toBe(null);
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(element.props).toEqual({foo: '56'});
|
||||
});
|
||||
|
||||
@@ -237,7 +265,6 @@ describe('ReactElement', () => {
|
||||
});
|
||||
|
||||
it('merges an additional argument onto the children prop', () => {
|
||||
spyOn(console, 'error');
|
||||
var a = 1;
|
||||
var element = React.createFactory(ComponentClass)(
|
||||
{
|
||||
@@ -246,20 +273,16 @@ describe('ReactElement', () => {
|
||||
a,
|
||||
);
|
||||
expect(element.props.children).toBe(a);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not override children if no rest args are provided', () => {
|
||||
spyOn(console, 'error');
|
||||
var element = React.createFactory(ComponentClass)({
|
||||
children: 'text',
|
||||
});
|
||||
expect(element.props.children).toBe('text');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('overrides children if null is provided as an argument', () => {
|
||||
spyOn(console, 'error');
|
||||
var element = React.createFactory(ComponentClass)(
|
||||
{
|
||||
children: 'text',
|
||||
@@ -267,24 +290,19 @@ describe('ReactElement', () => {
|
||||
null,
|
||||
);
|
||||
expect(element.props.children).toBe(null);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('merges rest arguments onto the children prop in an array', () => {
|
||||
spyOn(console, 'error');
|
||||
var a = 1;
|
||||
var b = 2;
|
||||
var c = 3;
|
||||
var element = React.createFactory(ComponentClass)(null, a, b, c);
|
||||
expect(element.props.children).toEqual([1, 2, 3]);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
// NOTE: We're explicitly not using JSX here. This is intended to test
|
||||
// classic JS without JSX.
|
||||
it('allows static methods to be called using the type property', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
class StaticMethodComponentClass extends React.Component {
|
||||
render() {
|
||||
return React.createElement('div');
|
||||
@@ -294,7 +312,6 @@ describe('ReactElement', () => {
|
||||
|
||||
var element = React.createElement(StaticMethodComponentClass);
|
||||
expect(element.type.someStaticMethod()).toBe('someReturnValue');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
// NOTE: We're explicitly not using JSX here. This is intended to test
|
||||
@@ -376,16 +393,25 @@ describe('ReactElement', () => {
|
||||
render() {
|
||||
var el = <div className="moo" />;
|
||||
|
||||
expect(function() {
|
||||
if (__DEV__) {
|
||||
expect(function() {
|
||||
el.props.className = 'quack';
|
||||
}).toThrow();
|
||||
expect(el.props.className).toBe('moo');
|
||||
} else {
|
||||
el.props.className = 'quack';
|
||||
}).toThrow();
|
||||
expect(el.props.className).toBe('moo');
|
||||
expect(el.props.className).toBe('quack');
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
||||
var outer = ReactTestUtils.renderIntoDocument(<Outer color="orange" />);
|
||||
expect(ReactDOM.findDOMNode(outer).className).toBe('moo');
|
||||
if (__DEV__) {
|
||||
expect(ReactDOM.findDOMNode(outer).className).toBe('moo');
|
||||
} else {
|
||||
expect(ReactDOM.findDOMNode(outer).className).toBe('quack');
|
||||
}
|
||||
});
|
||||
|
||||
it('throws when adding a prop (in dev) after element creation', () => {
|
||||
@@ -394,11 +420,15 @@ describe('ReactElement', () => {
|
||||
render() {
|
||||
var el = <div>{this.props.sound}</div>;
|
||||
|
||||
expect(function() {
|
||||
if (__DEV__) {
|
||||
expect(function() {
|
||||
el.props.className = 'quack';
|
||||
}).toThrow();
|
||||
expect(el.props.className).toBe(undefined);
|
||||
} else {
|
||||
el.props.className = 'quack';
|
||||
}).toThrow();
|
||||
|
||||
expect(el.props.className).toBe(undefined);
|
||||
expect(el.props.className).toBe('quack');
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
@@ -406,11 +436,14 @@ describe('ReactElement', () => {
|
||||
Outer.defaultProps = {sound: 'meow'};
|
||||
var outer = ReactDOM.render(<Outer />, container);
|
||||
expect(ReactDOM.findDOMNode(outer).textContent).toBe('meow');
|
||||
expect(ReactDOM.findDOMNode(outer).className).toBe('');
|
||||
if (__DEV__) {
|
||||
expect(ReactDOM.findDOMNode(outer).className).toBe('');
|
||||
} else {
|
||||
expect(ReactDOM.findDOMNode(outer).className).toBe('quack');
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn for NaN props', () => {
|
||||
spyOn(console, 'error');
|
||||
class Test extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
@@ -418,7 +451,6 @@ describe('ReactElement', () => {
|
||||
}
|
||||
var test = ReactTestUtils.renderIntoDocument(<Test value={+undefined} />);
|
||||
expect(test.props.value).toBeNaN();
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
// NOTE: We're explicitly not using JSX here. This is intended to test
|
||||
|
||||
@@ -257,42 +257,32 @@ describe('ReactElementClone', () => {
|
||||
});
|
||||
|
||||
it('warns for keys for arrays of elements in rest args', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
React.cloneElement(<div />, null, [<div />, <div />]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warns for arrays of elements with keys', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
React.cloneElement(<div />, null, [<div key="#1" />, <div key="#2" />]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn when the element is directly in rest args', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
React.cloneElement(<div />, null, <div />, <div />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn when the array contains a non-element', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
React.cloneElement(<div />, null, [{}, {}]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('should check declared prop types after clone', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class Component extends React.Component {
|
||||
static propTypes = {
|
||||
color: PropTypes.string.isRequired,
|
||||
@@ -314,15 +304,17 @@ describe('ReactElementClone', () => {
|
||||
}
|
||||
}
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(GrandParent));
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `Component`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in Component (created by GrandParent)\n' +
|
||||
' in Parent (created by GrandParent)\n' +
|
||||
' in GrandParent',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `Component`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in Component (created by GrandParent)\n' +
|
||||
' in Parent (created by GrandParent)\n' +
|
||||
' in GrandParent',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should ignore key and ref warning getters', () => {
|
||||
@@ -347,8 +339,10 @@ describe('ReactElementClone', () => {
|
||||
expect(clone.type).toBe(ComponentClass);
|
||||
expect(clone.key).toBe('12');
|
||||
expect(clone.ref).toBe('34');
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(clone.props).toEqual({foo: 'ef'});
|
||||
});
|
||||
|
||||
@@ -367,8 +361,10 @@ describe('ReactElementClone', () => {
|
||||
expect(clone.type).toBe(ComponentClass);
|
||||
expect(clone.key).toBe('null');
|
||||
expect(clone.ref).toBe(null);
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
if (__DEV__) {
|
||||
expect(Object.isFrozen(element)).toBe(true);
|
||||
expect(Object.isFrozen(element.props)).toBe(true);
|
||||
}
|
||||
expect(clone.props).toEqual({foo: 'ef'});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,19 +39,21 @@ describe('ReactElementValidator', () => {
|
||||
});
|
||||
|
||||
it('warns for keys for arrays of elements in rest args', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var Component = React.createFactory(ComponentClass);
|
||||
|
||||
Component(null, [Component(), Component()]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for keys for arrays of elements with owner info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var Component = React.createFactory(ComponentClass);
|
||||
|
||||
class InnerClass extends React.Component {
|
||||
@@ -70,16 +72,18 @@ describe('ReactElementValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(ComponentWrapper));
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
'\n\nCheck the render method of `InnerClass`. ' +
|
||||
'It was passed a child from ComponentWrapper. ',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
'\n\nCheck the render method of `InnerClass`. ' +
|
||||
'It was passed a child from ComponentWrapper. ',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for keys for arrays with no owner or parent info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Anonymous() {
|
||||
return <div />;
|
||||
@@ -89,31 +93,35 @@ describe('ReactElementValidator', () => {
|
||||
var divs = [<div />, <div />];
|
||||
ReactTestUtils.renderIntoDocument(<Anonymous>{divs}</Anonymous>);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Each child in an array or iterator should have a unique ' +
|
||||
'"key" prop. See https://fb.me/react-warning-keys for more information.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Each child in an array or iterator should have a unique ' +
|
||||
'"key" prop. See https://fb.me/react-warning-keys for more information.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for keys for arrays of elements with no owner info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var divs = [<div />, <div />];
|
||||
ReactTestUtils.renderIntoDocument(<div>{divs}</div>);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Each child in an array or iterator should have a unique ' +
|
||||
'"key" prop.\n\nCheck the top-level render call using <div>. See ' +
|
||||
'https://fb.me/react-warning-keys for more information.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Each child in an array or iterator should have a unique ' +
|
||||
'"key" prop.\n\nCheck the top-level render call using <div>. See ' +
|
||||
'https://fb.me/react-warning-keys for more information.\n' +
|
||||
' in div (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for keys with component stack info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function Component() {
|
||||
return <div>{[<div />, <div />]}</div>;
|
||||
@@ -129,21 +137,21 @@ describe('ReactElementValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<GrandParent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Each child in an array or iterator should have a unique ' +
|
||||
'"key" prop.\n\nCheck the render method of `Component`. See ' +
|
||||
'https://fb.me/react-warning-keys for more information.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Each child in an array or iterator should have a unique ' +
|
||||
'"key" prop.\n\nCheck the render method of `Component`. See ' +
|
||||
'https://fb.me/react-warning-keys for more information.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn for keys when passing children down', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
function Wrapper(props) {
|
||||
return (
|
||||
<div>
|
||||
@@ -159,12 +167,10 @@ describe('ReactElementValidator', () => {
|
||||
<span />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('warns for keys for iterables of elements in rest args', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var Component = React.createFactory(ComponentClass);
|
||||
|
||||
var iterable = {
|
||||
@@ -181,23 +187,21 @@ describe('ReactElementValidator', () => {
|
||||
|
||||
Component(null, iterable);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warns for arrays of elements with keys', () => {
|
||||
spyOn(console, 'error');
|
||||
var Component = React.createFactory(ComponentClass);
|
||||
|
||||
Component(null, [Component({key: '#1'}), Component({key: '#2'})]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warns for iterable elements with keys', () => {
|
||||
spyOn(console, 'error');
|
||||
var Component = React.createFactory(ComponentClass);
|
||||
|
||||
var iterable = {
|
||||
@@ -216,33 +220,25 @@ describe('ReactElementValidator', () => {
|
||||
};
|
||||
|
||||
Component(null, iterable);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn when the element is directly in rest args', () => {
|
||||
spyOn(console, 'error');
|
||||
var Component = React.createFactory(ComponentClass);
|
||||
|
||||
Component(null, Component(), Component());
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn when the array contains a non-element', () => {
|
||||
spyOn(console, 'error');
|
||||
var Component = React.createFactory(ComponentClass);
|
||||
|
||||
Component(null, [{}, {}]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('should give context for PropType errors in nested components.', () => {
|
||||
// In this test, we're making sure that if a proptype error is found in a
|
||||
// component, we give a small hint as to which parent instantiated that
|
||||
// component as per warnings about key usage in ReactElementValidator.
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
function MyComp(props) {
|
||||
return React.createElement('div', null, 'My color is ' + props.color);
|
||||
}
|
||||
@@ -253,58 +249,64 @@ describe('ReactElementValidator', () => {
|
||||
return React.createElement(MyComp, {color: 123});
|
||||
}
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (created by ParentComp)\n' +
|
||||
' in ParentComp',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (created by ParentComp)\n' +
|
||||
' in ParentComp',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('gives a helpful error when passing invalid types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
React.createElement(undefined);
|
||||
React.createElement(null);
|
||||
React.createElement(true);
|
||||
React.createElement({x: 17});
|
||||
React.createElement({});
|
||||
expectDev(console.error.calls.count()).toBe(5);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: undefined. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: null.',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(2)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: boolean.',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(3)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: object.',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(4)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: object. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(5);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: undefined. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: null.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(2)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: boolean.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(3)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: object.',
|
||||
);
|
||||
expect(console.error.calls.argsFor(4)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: object. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.',
|
||||
);
|
||||
}
|
||||
React.createElement('div');
|
||||
expectDev(console.error.calls.count()).toBe(5);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(5);
|
||||
}
|
||||
});
|
||||
|
||||
it('includes the owner name when passing null, undefined, boolean, or number', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
function ParentComp() {
|
||||
return React.createElement(null);
|
||||
}
|
||||
@@ -312,20 +314,22 @@ describe('ReactElementValidator', () => {
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
|
||||
}).toThrowError(
|
||||
'Element type is invalid: expected a string (for built-in components) ' +
|
||||
'or a class/function (for composite components) but got: null.\n\nCheck ' +
|
||||
'the render method of `ParentComp`.',
|
||||
);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: null.\n\nCheck the render method of `ParentComp`.' +
|
||||
'\n in ParentComp',
|
||||
'or a class/function (for composite components) but got: null.' +
|
||||
(__DEV__ ? '\n\nCheck the render method of `ParentComp`.' : ''),
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: null.' +
|
||||
'\n\nCheck the render method of `ParentComp`.\n in ParentComp',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should check default prop values', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
static propTypes = {prop: PropTypes.string.isRequired};
|
||||
@@ -337,16 +341,18 @@ describe('ReactElementValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(React.createElement(Component));
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`Component`, but its value is `null`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`Component`, but its value is `null`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not check the default for explicit null', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
static propTypes = {prop: PropTypes.string.isRequired};
|
||||
@@ -360,16 +366,18 @@ describe('ReactElementValidator', () => {
|
||||
React.createElement(Component, {prop: null}),
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`Component`, but its value is `null`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`Component`, but its value is `null`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should check declared prop types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -385,31 +393,35 @@ describe('ReactElementValidator', () => {
|
||||
React.createElement(Component, {prop: 42}),
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'The prop `prop` is marked as required in `Component`, but its value ' +
|
||||
'is `undefined`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'The prop `prop` is marked as required in `Component`, but its value ' +
|
||||
'is `undefined`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`Component`, expected `string`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`Component`, expected `string`.\n' +
|
||||
' in Component',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
React.createElement(Component, {prop: 'string'}),
|
||||
);
|
||||
|
||||
// Should not error for strings
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
if (__DEV__) {
|
||||
// Should not error for strings
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if a PropType creator is used as a PropType', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Component extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -424,35 +436,40 @@ describe('ReactElementValidator', () => {
|
||||
React.createElement(Component, {myProp: {value: 'hi'}}),
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: type specification of prop `myProp` is invalid; ' +
|
||||
'the type checker function must return `null` or an `Error` but ' +
|
||||
'returned a function. You may have forgotten to pass an argument to ' +
|
||||
'the type checker creator (arrayOf, instanceOf, objectOf, oneOf, ' +
|
||||
'oneOfType, and shape all require an argument).',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: type specification of prop `myProp` is invalid; ' +
|
||||
'the type checker function must return `null` or an `Error` but ' +
|
||||
'returned a function. You may have forgotten to pass an argument to ' +
|
||||
'the type checker creator (arrayOf, instanceOf, objectOf, oneOf, ' +
|
||||
'oneOfType, and shape all require an argument).',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when accessing .type on an element factory', () => {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
function TestComponent() {
|
||||
return <div />;
|
||||
}
|
||||
var TestFactory = React.createFactory(TestComponent);
|
||||
expect(TestFactory.type).toBe(TestComponent);
|
||||
expectDev(console.warn.calls.count()).toBe(1);
|
||||
expectDev(console.warn.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Factory.type is deprecated. Access the class directly before ' +
|
||||
'passing it to createFactory.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(1);
|
||||
expect(console.warn.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Factory.type is deprecated. Access the class directly before ' +
|
||||
'passing it to createFactory.',
|
||||
);
|
||||
}
|
||||
// Warn once, not again
|
||||
expect(TestFactory.type).toBe(TestComponent);
|
||||
expectDev(console.warn.calls.count()).toBe(1);
|
||||
if (__DEV__) {
|
||||
expect(console.warn.calls.count()).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn when using DOM node as children', () => {
|
||||
spyOn(console, 'error');
|
||||
class DOMContainer extends React.Component {
|
||||
render() {
|
||||
return <div />;
|
||||
@@ -465,7 +482,6 @@ describe('ReactElementValidator', () => {
|
||||
var node = document.createElement('div');
|
||||
// This shouldn't cause a stack overflow or any other problems (#3883)
|
||||
ReactTestUtils.renderIntoDocument(<DOMContainer>{node}</DOMContainer>);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('should not enumerate enumerable numbers (#4776)', () => {
|
||||
@@ -505,16 +521,18 @@ describe('ReactElementValidator', () => {
|
||||
});
|
||||
|
||||
it('does not blow up on key warning with undefined type', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var Foo = undefined;
|
||||
void <Foo>{[<div />]}</Foo>;
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: undefined. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.\n\nCheck your code at **.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: undefined. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.\n\nCheck your code at **.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,7 +62,11 @@ describe('ReactJSXElement', () => {
|
||||
|
||||
it('returns an immutable element', () => {
|
||||
var element = <Component />;
|
||||
expect(() => (element.type = 'div')).toThrow();
|
||||
if (__DEV__) {
|
||||
expect(() => (element.type = 'div')).toThrow();
|
||||
} else {
|
||||
expect(() => (element.type = 'div')).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not reuse the object that is spread into props', () => {
|
||||
@@ -94,25 +98,19 @@ describe('ReactJSXElement', () => {
|
||||
});
|
||||
|
||||
it('merges JSX children onto the children prop', () => {
|
||||
spyOn(console, 'error');
|
||||
var a = 1;
|
||||
var element = <Component children="text">{a}</Component>;
|
||||
expect(element.props.children).toBe(a);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not override children if no JSX children are provided', () => {
|
||||
spyOn(console, 'error');
|
||||
var element = <Component children="text" />;
|
||||
expect(element.props.children).toBe('text');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('overrides children if null is provided as a JSX child', () => {
|
||||
spyOn(console, 'error');
|
||||
var element = <Component children="text">{null}</Component>;
|
||||
expect(element.props.children).toBe(null);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('overrides children if undefined is provided as an argument', () => {
|
||||
@@ -128,7 +126,6 @@ describe('ReactJSXElement', () => {
|
||||
});
|
||||
|
||||
it('merges JSX children onto the children prop in an array', () => {
|
||||
spyOn(console, 'error');
|
||||
var a = 1;
|
||||
var b = 2;
|
||||
var c = 3;
|
||||
@@ -140,12 +137,9 @@ describe('ReactJSXElement', () => {
|
||||
</Component>
|
||||
);
|
||||
expect(element.props.children).toEqual([1, 2, 3]);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('allows static methods to be called using the type property', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
class StaticMethodComponent {
|
||||
static someStaticMethod() {
|
||||
return 'someReturnValue';
|
||||
@@ -157,7 +151,6 @@ describe('ReactJSXElement', () => {
|
||||
|
||||
var element = <StaticMethodComponent />;
|
||||
expect(element.type.someStaticMethod()).toBe('someReturnValue');
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('identifies valid elements', () => {
|
||||
|
||||
@@ -51,20 +51,22 @@ describe('ReactJSXElementValidator', () => {
|
||||
});
|
||||
|
||||
it('warns for keys for arrays of elements in children position', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component>{[<Component />, <Component />]}</Component>,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for keys for arrays of elements with owner info', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class InnerComponent extends React.Component {
|
||||
render() {
|
||||
@@ -80,16 +82,18 @@ describe('ReactJSXElementValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ComponentWrapper />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
'\n\nCheck the render method of `InnerComponent`. ' +
|
||||
'It was passed a child from ComponentWrapper. ',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.' +
|
||||
'\n\nCheck the render method of `InnerComponent`. ' +
|
||||
'It was passed a child from ComponentWrapper. ',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for fragments with illegal attributes', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Foo extends React.Component {
|
||||
render() {
|
||||
@@ -103,16 +107,18 @@ describe('ReactJSXElementValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Foo />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain('Invalid prop `');
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'` supplied to `React.Fragment`. React.Fragment ' +
|
||||
'can only have `key` and `children` props.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain('Invalid prop `');
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'` supplied to `React.Fragment`. React.Fragment ' +
|
||||
'can only have `key` and `children` props.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for fragments with refs', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Foo extends React.Component {
|
||||
render() {
|
||||
@@ -129,14 +135,16 @@ describe('ReactJSXElementValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Foo />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Invalid attribute `ref` supplied to `React.Fragment`.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Invalid attribute `ref` supplied to `React.Fragment`.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns for keys for iterables of elements in rest args', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var iterable = {
|
||||
'@@iterator': function() {
|
||||
@@ -152,10 +160,12 @@ describe('ReactJSXElementValidator', () => {
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Each child in an array or iterator should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn for fragments of multiple elements without keys', () => {
|
||||
@@ -168,7 +178,7 @@ describe('ReactJSXElementValidator', () => {
|
||||
});
|
||||
|
||||
it('warns for fragments of multiple elements with same key', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<React.Fragment>
|
||||
@@ -178,25 +188,21 @@ describe('ReactJSXElementValidator', () => {
|
||||
</React.Fragment>,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Encountered two children with the same key, `a`.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Encountered two children with the same key, `a`.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn for arrays of elements with keys', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component>{[<Component key="#1" />, <Component key="#2" />]}</Component>,
|
||||
);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn for iterable elements with keys', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var iterable = {
|
||||
'@@iterator': function() {
|
||||
var i = 0;
|
||||
@@ -213,13 +219,9 @@ describe('ReactJSXElementValidator', () => {
|
||||
};
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn for numeric keys in entry iterable as a child', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var iterable = {
|
||||
'@@iterator': function() {
|
||||
var i = 0;
|
||||
@@ -234,8 +236,6 @@ describe('ReactJSXElementValidator', () => {
|
||||
iterable.entries = iterable['@@iterator'];
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component>{iterable}</Component>);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn when the element is directly as children', () => {
|
||||
@@ -255,7 +255,7 @@ describe('ReactJSXElementValidator', () => {
|
||||
// In this test, we're making sure that if a proptype error is found in a
|
||||
// component, we give a small hint as to which parent instantiated that
|
||||
// component as per warnings about key usage in ReactElementValidator.
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class MyComp extends React.Component {
|
||||
render() {
|
||||
return <div>My color is {this.color}</div>;
|
||||
@@ -270,17 +270,19 @@ describe('ReactJSXElementValidator', () => {
|
||||
}
|
||||
}
|
||||
ReactTestUtils.renderIntoDocument(<ParentComp />);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (at **)\n' +
|
||||
' in ParentComp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (at **)\n' +
|
||||
' in ParentComp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should update component stack after receiving next element', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
function MyComp() {
|
||||
return null;
|
||||
}
|
||||
@@ -303,17 +305,19 @@ describe('ReactJSXElementValidator', () => {
|
||||
ReactDOM.render(<ParentComp warn={false} />, container);
|
||||
ReactDOM.render(<ParentComp warn={true} />, container);
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
// The warning should have the full stack with line numbers.
|
||||
// If it doesn't, it means we're using information from the old element.
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (at **)\n' +
|
||||
' in MiddleComp (at **)\n' +
|
||||
' in ParentComp (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
// The warning should have the full stack with line numbers.
|
||||
// If it doesn't, it means we're using information from the old element.
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`.\n' +
|
||||
' in MyComp (at **)\n' +
|
||||
' in MiddleComp (at **)\n' +
|
||||
' in ParentComp (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('gives a helpful error when passing null, undefined, or boolean', () => {
|
||||
@@ -321,88 +325,100 @@ describe('ReactJSXElementValidator', () => {
|
||||
var Null = null;
|
||||
var True = true;
|
||||
var Div = 'div';
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
void <Undefined />;
|
||||
void <Null />;
|
||||
void <True />;
|
||||
expectDev(console.error.calls.count()).toBe(3);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: undefined. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: null.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: boolean.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: undefined. You likely forgot to export your ' +
|
||||
"component from the file it's defined in, or you might have mixed up " +
|
||||
'default and named imports.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: null.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(2)[0])).toBe(
|
||||
'Warning: React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function (for composite ' +
|
||||
'components) but got: boolean.' +
|
||||
'\n\nCheck your code at **.',
|
||||
);
|
||||
}
|
||||
void <Div />;
|
||||
expectDev(console.error.calls.count()).toBe(3);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(3);
|
||||
}
|
||||
});
|
||||
|
||||
it('should check default prop values', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
RequiredPropComponent.defaultProps = {prop: null};
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<RequiredPropComponent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`RequiredPropComponent`, but its value is `null`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`RequiredPropComponent`, but its value is `null`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not check the default for explicit null', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={null} />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`RequiredPropComponent`, but its value is `null`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: The prop `prop` is marked as required in ' +
|
||||
'`RequiredPropComponent`, but its value is `null`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should check declared prop types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<RequiredPropComponent />);
|
||||
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop={42} />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'The prop `prop` is marked as required in `RequiredPropComponent`, but ' +
|
||||
'its value is `undefined`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'The prop `prop` is marked as required in `RequiredPropComponent`, but ' +
|
||||
'its value is `undefined`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`RequiredPropComponent`, expected `string`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Failed prop type: ' +
|
||||
'Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`RequiredPropComponent`, expected `string`.\n' +
|
||||
' in RequiredPropComponent (at **)',
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<RequiredPropComponent prop="string" />);
|
||||
|
||||
// Should not error for strings
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
if (__DEV__) {
|
||||
// Should not error for strings
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn on invalid prop types', () => {
|
||||
@@ -410,7 +426,7 @@ describe('ReactJSXElementValidator', () => {
|
||||
// for us to issue a warning earlier than element creation when the error
|
||||
// actually occurs. Since this step is skipped in production, we should just
|
||||
// warn instead of throwing for this case.
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class NullPropTypeComponent extends React.Component {
|
||||
render() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
@@ -420,15 +436,17 @@ describe('ReactJSXElementValidator', () => {
|
||||
prop: null,
|
||||
};
|
||||
ReactTestUtils.renderIntoDocument(<NullPropTypeComponent />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'NullPropTypeComponent: prop type `prop` is invalid; it must be a ' +
|
||||
'function, usually from the `prop-types` package,',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'NullPropTypeComponent: prop type `prop` is invalid; it must be a ' +
|
||||
'function, usually from the `prop-types` package,',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn on invalid context types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class NullContextTypeComponent extends React.Component {
|
||||
render() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
@@ -438,15 +456,17 @@ describe('ReactJSXElementValidator', () => {
|
||||
prop: null,
|
||||
};
|
||||
ReactTestUtils.renderIntoDocument(<NullContextTypeComponent />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'NullContextTypeComponent: context type `prop` is invalid; it must ' +
|
||||
'be a function, usually from the `prop-types` package,',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'NullContextTypeComponent: context type `prop` is invalid; it must ' +
|
||||
'be a function, usually from the `prop-types` package,',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn if getDefaultProps is specificed on the class', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
class GetDefaultPropsComponent extends React.Component {
|
||||
render() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
@@ -456,10 +476,12 @@ describe('ReactJSXElementValidator', () => {
|
||||
prop: 'foo',
|
||||
});
|
||||
ReactTestUtils.renderIntoDocument(<GetDefaultPropsComponent />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'getDefaultProps is only used on classic React.createClass definitions.' +
|
||||
' Use a static property named `defaultProps` instead.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'getDefaultProps is only used on classic React.createClass definitions.' +
|
||||
' Use a static property named `defaultProps` instead.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('ReactPureComponent', () => {
|
||||
});
|
||||
|
||||
it('can override shouldComponentUpdate', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var renders = 0;
|
||||
class Component extends React.PureComponent {
|
||||
render() {
|
||||
@@ -76,13 +76,15 @@ describe('ReactPureComponent', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component />, container);
|
||||
ReactDOM.render(<Component />, container);
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'Component has a method called shouldComponentUpdate(). ' +
|
||||
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
|
||||
'Please extend React.Component if shouldComponentUpdate is used.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'Component has a method called shouldComponentUpdate(). ' +
|
||||
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
|
||||
'Please extend React.Component if shouldComponentUpdate is used.',
|
||||
);
|
||||
}
|
||||
expect(renders).toBe(2);
|
||||
});
|
||||
|
||||
@@ -101,7 +103,7 @@ describe('ReactPureComponent', () => {
|
||||
});
|
||||
|
||||
it('should warn when shouldComponentUpdate is defined on React.PureComponent', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class PureComponent extends React.PureComponent {
|
||||
shouldComponentUpdate() {
|
||||
@@ -114,12 +116,14 @@ describe('ReactPureComponent', () => {
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<PureComponent />, container);
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'PureComponent has a method called shouldComponentUpdate(). ' +
|
||||
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
|
||||
'Please extend React.Component if shouldComponentUpdate is used.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'PureComponent has a method called shouldComponentUpdate(). ' +
|
||||
'shouldComponentUpdate should not be used when extending React.PureComponent. ' +
|
||||
'Please extend React.Component if shouldComponentUpdate is used.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -315,15 +315,17 @@ describe('ReactTypeScriptClass', function() {
|
||||
});
|
||||
|
||||
it('throws if no render function is defined', function() {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
expect(() => ReactDOM.render(React.createElement(Empty), container)).toThrow();
|
||||
|
||||
expect((<any>console.error).calls.count()).toBe(1);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Empty(...): No `render` method found on the returned ' +
|
||||
'component instance: you may have forgotten to define `render`.'
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect((<any>console.error).calls.count()).toBe(1);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Empty(...): No `render` method found on the returned ' +
|
||||
'component instance: you may have forgotten to define `render`.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('renders a simple stateless component with prop', function() {
|
||||
@@ -360,18 +362,31 @@ describe('ReactTypeScriptClass', function() {
|
||||
});
|
||||
|
||||
it('should throw with non-object in the initial state property', function() {
|
||||
expect(() => test(React.createElement(ArrayState), 'span', ''))
|
||||
.toThrowError(
|
||||
'ArrayState.state: must be set to an object or null'
|
||||
);
|
||||
expect(() => test(React.createElement(StringState), 'span', ''))
|
||||
.toThrowError(
|
||||
'StringState.state: must be set to an object or null'
|
||||
);
|
||||
expect(() => test(React.createElement(NumberState), 'span', ''))
|
||||
.toThrowError(
|
||||
'NumberState.state: must be set to an object or null'
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(() => test(React.createElement(ArrayState), 'SPAN', ''))
|
||||
.toThrowError(
|
||||
'ArrayState.state: must be set to an object or null'
|
||||
);
|
||||
expect(() => test(React.createElement(StringState), 'SPAN', ''))
|
||||
.toThrowError(
|
||||
'StringState.state: must be set to an object or null'
|
||||
);
|
||||
expect(() => test(React.createElement(NumberState), 'SPAN', ''))
|
||||
.toThrowError(
|
||||
'NumberState.state: must be set to an object or null'
|
||||
);
|
||||
} else {
|
||||
// This is a difference between development and production.
|
||||
// I'm not sure if this is intentional, as generally we avoid this.
|
||||
// TODO: investigate if this was intentional or an oversight.
|
||||
// https://github.com/facebook/react/issues/11618
|
||||
expect(() => test(React.createElement(ArrayState), 'SPAN', ''))
|
||||
.not.toThrowError();
|
||||
expect(() => test(React.createElement(StringState), 'SPAN', ''))
|
||||
.not.toThrowError();
|
||||
expect(() => test(React.createElement(NumberState), 'SPAN', ''))
|
||||
.not.toThrowError();
|
||||
}
|
||||
});
|
||||
|
||||
it('should render with null in the initial state property', function() {
|
||||
@@ -431,32 +446,35 @@ describe('ReactTypeScriptClass', function() {
|
||||
|
||||
it('warns when classic properties are defined on the instance, ' +
|
||||
'but does not invoke them.', function() {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
getInitialStateWasCalled = false;
|
||||
getDefaultPropsWasCalled = false;
|
||||
test(React.createElement(ClassicProperties), 'SPAN', 'foo');
|
||||
expect(getInitialStateWasCalled).toBe(false);
|
||||
expect(getDefaultPropsWasCalled).toBe(false);
|
||||
expect((<any>console.error).calls.count()).toBe(4);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toContain(
|
||||
'getInitialState was defined on ClassicProperties, ' +
|
||||
'a plain JavaScript class.'
|
||||
);
|
||||
expect((<any>console.error).calls.argsFor(1)[0]).toContain(
|
||||
'getDefaultProps was defined on ClassicProperties, ' +
|
||||
'a plain JavaScript class.'
|
||||
);
|
||||
expect((<any>console.error).calls.argsFor(2)[0]).toContain(
|
||||
'propTypes was defined as an instance property on ClassicProperties.'
|
||||
);
|
||||
expect((<any>console.error).calls.argsFor(3)[0]).toContain(
|
||||
'contextTypes was defined as an instance property on ClassicProperties.'
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
expect((<any>console.error).calls.count()).toBe(4);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toContain(
|
||||
'getInitialState was defined on ClassicProperties, ' +
|
||||
'a plain JavaScript class.'
|
||||
);
|
||||
expect((<any>console.error).calls.argsFor(1)[0]).toContain(
|
||||
'getDefaultProps was defined on ClassicProperties, ' +
|
||||
'a plain JavaScript class.'
|
||||
);
|
||||
expect((<any>console.error).calls.argsFor(2)[0]).toContain(
|
||||
'propTypes was defined as an instance property on ClassicProperties.'
|
||||
);
|
||||
expect((<any>console.error).calls.argsFor(3)[0]).toContain(
|
||||
'contextTypes was defined as an instance property on ClassicProperties.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn about getInitialState() on class components ' +
|
||||
'if state is also defined.', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
class Example extends React.Component {
|
||||
state = {};
|
||||
@@ -469,51 +487,59 @@ describe('ReactTypeScriptClass', function() {
|
||||
}
|
||||
|
||||
test(React.createElement(Example), 'SPAN', 'foo');
|
||||
expect((<any>console.error).calls.count()).toBe(0);
|
||||
if (__DEV__) {
|
||||
expect((<any>console.error).calls.count()).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when misspelling shouldComponentUpdate', function() {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
test(React.createElement(MisspelledComponent1), 'SPAN', 'foo');
|
||||
|
||||
expect((<any>console.error).calls.count()).toBe(1);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'MisspelledComponent1 has a method called componentShouldUpdate(). Did ' +
|
||||
'you mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.'
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect((<any>console.error).calls.count()).toBe(1);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'MisspelledComponent1 has a method called componentShouldUpdate(). Did ' +
|
||||
'you mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when misspelling componentWillReceiveProps', function() {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
test(React.createElement(MisspelledComponent2), 'SPAN', 'foo');
|
||||
|
||||
expect((<any>console.error).calls.count()).toBe(1);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'MisspelledComponent2 has a method called componentWillRecieveProps(). ' +
|
||||
'Did you mean componentWillReceiveProps()?'
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect((<any>console.error).calls.count()).toBe(1);
|
||||
expect((<any>console.error).calls.argsFor(0)[0]).toBe(
|
||||
'Warning: ' +
|
||||
'MisspelledComponent2 has a method called componentWillRecieveProps(). ' +
|
||||
'Did you mean componentWillReceiveProps()?'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw AND warn when trying to access classic APIs', function() {
|
||||
spyOn(console, 'warn');
|
||||
spyOnDev(console, 'warn');
|
||||
var instance = test(
|
||||
React.createElement(Inner, {name: 'foo'}),
|
||||
'DIV','foo'
|
||||
);
|
||||
expect(() => instance.replaceState({})).toThrow();
|
||||
expect(() => instance.isMounted()).toThrow();
|
||||
expect((<any>console.warn).calls.count()).toBe(2);
|
||||
expect((<any>console.warn).calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
);
|
||||
expect((<any>console.warn).calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes'
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect((<any>console.warn).calls.count()).toBe(2);
|
||||
expect((<any>console.warn).calls.argsFor(0)[0]).toContain(
|
||||
'replaceState(...) is deprecated in plain JavaScript React classes'
|
||||
);
|
||||
expect((<any>console.warn).calls.argsFor(1)[0]).toContain(
|
||||
'isMounted(...) is deprecated in plain JavaScript React classes'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('supports this.context passed via getChildContext', function() {
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('create-react-class-integration', () => {
|
||||
});
|
||||
|
||||
it('should warn on invalid prop types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
propTypes: {
|
||||
@@ -62,15 +62,17 @@ describe('create-react-class-integration', () => {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
});
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: prop type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: prop type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn on invalid context types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
contextTypes: {
|
||||
@@ -80,15 +82,17 @@ describe('create-react-class-integration', () => {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
});
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw on invalid child context types', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
createReactClass({
|
||||
displayName: 'Component',
|
||||
childContextTypes: {
|
||||
@@ -98,15 +102,17 @@ describe('create-react-class-integration', () => {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
});
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: child context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: child context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when misspelling shouldComponentUpdate', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
createReactClass({
|
||||
componentShouldUpdate: function() {
|
||||
@@ -116,12 +122,14 @@ describe('create-react-class-integration', () => {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: A component has a method called componentShouldUpdate(). Did you ' +
|
||||
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: A component has a method called componentShouldUpdate(). Did you ' +
|
||||
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.',
|
||||
);
|
||||
}
|
||||
|
||||
createReactClass({
|
||||
displayName: 'NamedComponent',
|
||||
@@ -132,16 +140,18 @@ describe('create-react-class-integration', () => {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' +
|
||||
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(2);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: NamedComponent has a method called componentShouldUpdate(). Did you ' +
|
||||
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
||||
'because the function is expected to return a value.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should warn when misspelling componentWillReceiveProps', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
createReactClass({
|
||||
componentWillRecieveProps: function() {
|
||||
return false;
|
||||
@@ -150,11 +160,13 @@ describe('create-react-class-integration', () => {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
|
||||
'mean componentWillReceiveProps()?',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
|
||||
'mean componentWillReceiveProps()?',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw if a reserved property is in statics', () => {
|
||||
@@ -183,7 +195,7 @@ describe('create-react-class-integration', () => {
|
||||
// TODO: Consider actually moving these to statics or drop this unit test.
|
||||
|
||||
xit('should warn when using deprecated non-static spec keys', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
createReactClass({
|
||||
mixins: [{}],
|
||||
propTypes: {
|
||||
@@ -199,23 +211,25 @@ describe('create-react-class-integration', () => {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expect(console.error.calls.count()).toBe(4);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'createClass(...): `mixins` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'createClass(...): `propTypes` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
);
|
||||
expect(console.error.calls.argsFor(2)[0]).toBe(
|
||||
'createClass(...): `contextTypes` is now a static property and ' +
|
||||
'should be defined inside "statics".',
|
||||
);
|
||||
expect(console.error.calls.argsFor(3)[0]).toBe(
|
||||
'createClass(...): `childContextTypes` is now a static property and ' +
|
||||
'should be defined inside "statics".',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(4);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'createClass(...): `mixins` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
);
|
||||
expect(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'createClass(...): `propTypes` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
);
|
||||
expect(console.error.calls.argsFor(2)[0]).toBe(
|
||||
'createClass(...): `contextTypes` is now a static property and ' +
|
||||
'should be defined inside "statics".',
|
||||
);
|
||||
expect(console.error.calls.argsFor(3)[0]).toBe(
|
||||
'createClass(...): `childContextTypes` is now a static property and ' +
|
||||
'should be defined inside "statics".',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should support statics', () => {
|
||||
@@ -328,7 +342,7 @@ describe('create-react-class-integration', () => {
|
||||
});
|
||||
|
||||
it('should throw when using legacy factories', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
var Component = createReactClass({
|
||||
render() {
|
||||
return <div />;
|
||||
@@ -336,11 +350,13 @@ describe('create-react-class-integration', () => {
|
||||
});
|
||||
|
||||
expect(() => Component()).toThrow();
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Something is calling a React component directly. Use a ' +
|
||||
'factory or JSX instead. See: https://fb.me/react-legacyfactory',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Something is calling a React component directly. Use a ' +
|
||||
'factory or JSX instead. See: https://fb.me/react-legacyfactory',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('replaceState and callback works', () => {
|
||||
@@ -363,7 +379,7 @@ describe('create-react-class-integration', () => {
|
||||
});
|
||||
|
||||
it('isMounted works', () => {
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
var ops = [];
|
||||
var instance;
|
||||
@@ -439,11 +455,13 @@ describe('create-react-class-integration', () => {
|
||||
'after unmount: false',
|
||||
]);
|
||||
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toEqual(
|
||||
'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
|
||||
'clean up subscriptions and pending requests in componentWillUnmount ' +
|
||||
'to prevent memory leaks.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toEqual(
|
||||
'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
|
||||
'clean up subscriptions and pending requests in componentWillUnmount ' +
|
||||
'to prevent memory leaks.',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,12 +15,14 @@ describe('ReactDOMFrameScheduling', () => {
|
||||
try {
|
||||
global.requestAnimationFrame = undefined;
|
||||
jest.resetModules();
|
||||
spyOn(console, 'error');
|
||||
spyOnDev(console, 'error');
|
||||
require('react-dom');
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'React depends on requestAnimationFrame.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
expect(console.error.calls.count()).toBe(1);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'React depends on requestAnimationFrame.',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
}
|
||||
|
||||
@@ -17,227 +17,189 @@ describe('ReactErrorUtils', () => {
|
||||
ReactErrorUtils = require('shared/ReactErrorUtils').default;
|
||||
});
|
||||
|
||||
// Run tests in both DEV and production
|
||||
describe(
|
||||
'invokeGuardedCallback (development)',
|
||||
invokeGuardedCallbackTests.bind(null, 'development'),
|
||||
);
|
||||
describe('invokeGuardedCallback (production)', () => {
|
||||
let oldProcess;
|
||||
beforeEach(() => {
|
||||
__DEV__ = false;
|
||||
|
||||
// Mutating process.env.NODE_ENV would cause our babel plugins to do the
|
||||
// wrong thing. If you change this, make sure to test with jest --no-cache.
|
||||
oldProcess = process;
|
||||
global.process = {
|
||||
...process,
|
||||
env: {...process.env, NODE_ENV: 'production'},
|
||||
};
|
||||
|
||||
jest.resetModules();
|
||||
ReactErrorUtils = require('shared/ReactErrorUtils').default;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__DEV__ = true;
|
||||
global.process = oldProcess;
|
||||
});
|
||||
|
||||
invokeGuardedCallbackTests('production');
|
||||
it(`it should rethrow caught errors`, () => {
|
||||
var err = new Error('foo');
|
||||
var callback = function() {
|
||||
throw err;
|
||||
};
|
||||
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
|
||||
'foo',
|
||||
callback,
|
||||
null,
|
||||
);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
|
||||
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
|
||||
});
|
||||
|
||||
function invokeGuardedCallbackTests(environment) {
|
||||
it(`it should rethrow caught errors (${environment})`, () => {
|
||||
var err = new Error('foo');
|
||||
var callback = function() {
|
||||
throw err;
|
||||
};
|
||||
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
|
||||
'foo',
|
||||
callback,
|
||||
null,
|
||||
);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
|
||||
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
|
||||
});
|
||||
it(`should call the callback the passed arguments`, () => {
|
||||
var callback = jest.fn();
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
callback,
|
||||
null,
|
||||
'arg1',
|
||||
'arg2',
|
||||
);
|
||||
expect(callback).toBeCalledWith('arg1', 'arg2');
|
||||
});
|
||||
|
||||
it(`should call the callback the passed arguments (${environment})`, () => {
|
||||
var callback = jest.fn();
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
callback,
|
||||
null,
|
||||
'arg1',
|
||||
'arg2',
|
||||
);
|
||||
expect(callback).toBeCalledWith('arg1', 'arg2');
|
||||
});
|
||||
it(`should call the callback with the provided context`, () => {
|
||||
var context = {didCall: false};
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
this.didCall = true;
|
||||
},
|
||||
context,
|
||||
);
|
||||
expect(context.didCall).toBe(true);
|
||||
});
|
||||
|
||||
it(`should call the callback with the provided context (${
|
||||
environment
|
||||
})`, () => {
|
||||
var context = {didCall: false};
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
this.didCall = true;
|
||||
},
|
||||
context,
|
||||
);
|
||||
expect(context.didCall).toBe(true);
|
||||
});
|
||||
it(`should catch errors`, () => {
|
||||
const error = new Error();
|
||||
const returnValue = ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
throw error;
|
||||
},
|
||||
null,
|
||||
'arg1',
|
||||
'arg2',
|
||||
);
|
||||
expect(returnValue).toBe(undefined);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(true);
|
||||
expect(ReactErrorUtils.clearCaughtError()).toBe(error);
|
||||
});
|
||||
|
||||
it(`should catch errors (${environment})`, () => {
|
||||
const error = new Error();
|
||||
const returnValue = ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
throw error;
|
||||
},
|
||||
null,
|
||||
'arg1',
|
||||
'arg2',
|
||||
);
|
||||
expect(returnValue).toBe(undefined);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(true);
|
||||
expect(ReactErrorUtils.clearCaughtError()).toBe(error);
|
||||
});
|
||||
it(`should return false from clearCaughtError if no error was thrown`, () => {
|
||||
var callback = jest.fn();
|
||||
ReactErrorUtils.invokeGuardedCallback('foo', callback, null);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
|
||||
expect(ReactErrorUtils.clearCaughtError).toThrow('no error was captured');
|
||||
});
|
||||
|
||||
it(`should return false from clearCaughtError if no error was thrown (${
|
||||
environment
|
||||
})`, () => {
|
||||
var callback = jest.fn();
|
||||
ReactErrorUtils.invokeGuardedCallback('foo', callback, null);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
|
||||
expect(ReactErrorUtils.clearCaughtError).toThrow(
|
||||
__DEV__ ? 'no error was captured' : 'Minified React error #198',
|
||||
);
|
||||
});
|
||||
|
||||
it(`can nest with same debug name (${environment})`, () => {
|
||||
const err1 = new Error();
|
||||
let err2;
|
||||
const err3 = new Error();
|
||||
let err4;
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
throw err1;
|
||||
},
|
||||
null,
|
||||
);
|
||||
err2 = ReactErrorUtils.clearCaughtError();
|
||||
throw err3;
|
||||
},
|
||||
null,
|
||||
);
|
||||
err4 = ReactErrorUtils.clearCaughtError();
|
||||
|
||||
expect(err2).toBe(err1);
|
||||
expect(err4).toBe(err3);
|
||||
});
|
||||
|
||||
it(`handles nested errors (${environment})`, () => {
|
||||
const err1 = new Error();
|
||||
let err2;
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
throw err1;
|
||||
},
|
||||
null,
|
||||
);
|
||||
err2 = ReactErrorUtils.clearCaughtError();
|
||||
},
|
||||
null,
|
||||
);
|
||||
// Returns null because inner error was already captured
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
|
||||
|
||||
expect(err2).toBe(err1);
|
||||
});
|
||||
|
||||
it('handles nested errors in separate renderers', () => {
|
||||
const ReactErrorUtils1 = require('shared/ReactErrorUtils').default;
|
||||
jest.resetModules();
|
||||
const ReactErrorUtils2 = require('shared/ReactErrorUtils').default;
|
||||
expect(ReactErrorUtils1).not.toEqual(ReactErrorUtils2);
|
||||
|
||||
let ops = [];
|
||||
|
||||
ReactErrorUtils1.invokeGuardedCallback(
|
||||
null,
|
||||
() => {
|
||||
ReactErrorUtils2.invokeGuardedCallback(
|
||||
null,
|
||||
() => {
|
||||
throw new Error('nested error');
|
||||
},
|
||||
null,
|
||||
);
|
||||
// ReactErrorUtils2 should catch the error
|
||||
ops.push(ReactErrorUtils2.hasCaughtError());
|
||||
ops.push(ReactErrorUtils2.clearCaughtError().message);
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
// ReactErrorUtils1 should not catch the error
|
||||
ops.push(ReactErrorUtils1.hasCaughtError());
|
||||
|
||||
expect(ops).toEqual([true, 'nested error', false]);
|
||||
});
|
||||
|
||||
if (environment === 'production') {
|
||||
// jsdom doesn't handle this properly, but Chrome and Firefox should. Test
|
||||
// this with a fixture.
|
||||
it('catches null values', () => {
|
||||
it(`can nest with same debug name`, () => {
|
||||
const err1 = new Error();
|
||||
let err2;
|
||||
const err3 = new Error();
|
||||
let err4;
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
null,
|
||||
'foo',
|
||||
function() {
|
||||
throw null; // eslint-disable-line no-throw-literal
|
||||
throw err1;
|
||||
},
|
||||
null,
|
||||
);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(true);
|
||||
expect(ReactErrorUtils.clearCaughtError()).toBe(null);
|
||||
});
|
||||
}
|
||||
err2 = ReactErrorUtils.clearCaughtError();
|
||||
throw err3;
|
||||
},
|
||||
null,
|
||||
);
|
||||
err4 = ReactErrorUtils.clearCaughtError();
|
||||
|
||||
it(`can be shimmed (${environment})`, () => {
|
||||
const ops = [];
|
||||
// Override the original invokeGuardedCallback
|
||||
ReactErrorUtils.injection.injectErrorUtils({
|
||||
invokeGuardedCallback(name, func, context, a) {
|
||||
ops.push(a);
|
||||
try {
|
||||
func.call(context, a);
|
||||
} catch (error) {
|
||||
this._hasCaughtError = true;
|
||||
this._caughtError = error;
|
||||
}
|
||||
},
|
||||
});
|
||||
expect(err2).toBe(err1);
|
||||
expect(err4).toBe(err3);
|
||||
});
|
||||
|
||||
var err = new Error('foo');
|
||||
var callback = function() {
|
||||
throw err;
|
||||
};
|
||||
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
|
||||
'foo',
|
||||
callback,
|
||||
it(`handles nested errors`, () => {
|
||||
const err1 = new Error();
|
||||
let err2;
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
'foo',
|
||||
function() {
|
||||
throw err1;
|
||||
},
|
||||
null,
|
||||
);
|
||||
err2 = ReactErrorUtils.clearCaughtError();
|
||||
},
|
||||
null,
|
||||
);
|
||||
// Returns null because inner error was already captured
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
|
||||
|
||||
expect(err2).toBe(err1);
|
||||
});
|
||||
|
||||
it('handles nested errors in separate renderers', () => {
|
||||
const ReactErrorUtils1 = require('shared/ReactErrorUtils').default;
|
||||
jest.resetModules();
|
||||
const ReactErrorUtils2 = require('shared/ReactErrorUtils').default;
|
||||
expect(ReactErrorUtils1).not.toEqual(ReactErrorUtils2);
|
||||
|
||||
let ops = [];
|
||||
|
||||
ReactErrorUtils1.invokeGuardedCallback(
|
||||
null,
|
||||
() => {
|
||||
ReactErrorUtils2.invokeGuardedCallback(
|
||||
null,
|
||||
() => {
|
||||
throw new Error('nested error');
|
||||
},
|
||||
null,
|
||||
);
|
||||
// ReactErrorUtils2 should catch the error
|
||||
ops.push(ReactErrorUtils2.hasCaughtError());
|
||||
ops.push(ReactErrorUtils2.clearCaughtError().message);
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
// ReactErrorUtils1 should not catch the error
|
||||
ops.push(ReactErrorUtils1.hasCaughtError());
|
||||
|
||||
expect(ops).toEqual([true, 'nested error', false]);
|
||||
});
|
||||
|
||||
if (!__DEV__) {
|
||||
// jsdom doesn't handle this properly, but Chrome and Firefox should. Test
|
||||
// this with a fixture.
|
||||
it('catches null values', () => {
|
||||
ReactErrorUtils.invokeGuardedCallback(
|
||||
null,
|
||||
function() {
|
||||
throw null; // eslint-disable-line no-throw-literal
|
||||
},
|
||||
null,
|
||||
'somearg',
|
||||
);
|
||||
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
|
||||
expect(ops).toEqual(['somearg']);
|
||||
expect(ReactErrorUtils.hasCaughtError()).toBe(true);
|
||||
expect(ReactErrorUtils.clearCaughtError()).toBe(null);
|
||||
});
|
||||
}
|
||||
|
||||
it(`can be shimmed`, () => {
|
||||
const ops = [];
|
||||
// Override the original invokeGuardedCallback
|
||||
ReactErrorUtils.injection.injectErrorUtils({
|
||||
invokeGuardedCallback(name, func, context, a) {
|
||||
ops.push(a);
|
||||
try {
|
||||
func.call(context, a);
|
||||
} catch (error) {
|
||||
this._hasCaughtError = true;
|
||||
this._caughtError = error;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var err = new Error('foo');
|
||||
var callback = function() {
|
||||
throw err;
|
||||
};
|
||||
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
|
||||
'foo',
|
||||
callback,
|
||||
null,
|
||||
'somearg',
|
||||
);
|
||||
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
|
||||
expect(ops).toEqual(['somearg']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,11 +11,29 @@
|
||||
var reactProdInvariant;
|
||||
|
||||
describe('reactProdInvariant', () => {
|
||||
let globalErrorMock;
|
||||
|
||||
beforeEach(() => {
|
||||
if (!__DEV__) {
|
||||
// In production, our Jest environment overrides the global Error
|
||||
// class in order to decode error messages automatically. However
|
||||
// this is a single test where we actually *don't* want to decode
|
||||
// them. So we assert that the OriginalError exists, and temporarily
|
||||
// set the global Error object back to it.
|
||||
globalErrorMock = global.Error;
|
||||
global.Error = globalErrorMock.OriginalError;
|
||||
expect(typeof global.Error).toBe('function');
|
||||
}
|
||||
jest.resetModules();
|
||||
reactProdInvariant = require('shared/reactProdInvariant').default;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (!__DEV__) {
|
||||
global.Error = globalErrorMock;
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw with the correct number of `%s`s in the URL', () => {
|
||||
expect(function() {
|
||||
reactProdInvariant(124, 'foo', 'bar');
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
set -e
|
||||
|
||||
node ./scripts/tasks/jest --coverage
|
||||
yarn test --coverage --runInBand
|
||||
if [ -z $CI_PULL_REQUEST ]; then
|
||||
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls
|
||||
fi
|
||||
|
||||
# TODO: should we also track prod code coverage somehow?
|
||||
# yarn test-prod --coverage
|
||||
|
||||
@@ -8,6 +8,10 @@ if [ $((1 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/test_coverage.sh')
|
||||
fi
|
||||
|
||||
if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
||||
COMMANDS_TO_RUN+=('yarn test-prod --runInBand')
|
||||
fi
|
||||
|
||||
if [ $((3 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
||||
COMMANDS_TO_RUN+=('node ./scripts/tasks/eslint')
|
||||
fi
|
||||
@@ -16,7 +20,7 @@ fi
|
||||
if [ $((0 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
||||
COMMANDS_TO_RUN+=('node ./scripts/prettier/index')
|
||||
COMMANDS_TO_RUN+=('node ./scripts/tasks/flow')
|
||||
COMMANDS_TO_RUN+=('node ./scripts/tasks/jest')
|
||||
COMMANDS_TO_RUN+=('yarn test --runInBand')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/build.sh')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/check_license.sh')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/check_modules.sh')
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
/* eslint-disable */
|
||||
global.__DEV__ = true;
|
||||
|
||||
// For testing DOM Fiber.
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (NODE_ENV !== 'development' && NODE_ENV !== 'production') {
|
||||
throw new Error('NODE_ENV must either be set to development or production.');
|
||||
}
|
||||
global.__DEV__ = NODE_ENV === 'development';
|
||||
|
||||
global.requestAnimationFrame = function(callback) {
|
||||
setTimeout(callback);
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -1,4 +1,5 @@
|
||||
declare var jasmine: any;
|
||||
declare var __DEV__: boolean;
|
||||
|
||||
declare function afterEach(fn: any): any;
|
||||
declare function beforeEach(fn: any): any;
|
||||
@@ -11,6 +12,7 @@ declare function expect(val: any): Expect;
|
||||
declare var jest: Jest;
|
||||
declare function pit(name: string, fn: any): void;
|
||||
declare function spyOn(obj: any, key: string): any;
|
||||
declare function spyOnDev(obj: any, key: string): any;
|
||||
declare function xdescribe(name: string, fn: any): void;
|
||||
declare function xit(name: string, fn: any): void;
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var babel = require('babel-core');
|
||||
|
||||
@@ -16,6 +16,12 @@ global.expect = function() {
|
||||
return expect.apply(this, arguments);
|
||||
};
|
||||
|
||||
global.spyOnDev = function(...args) {
|
||||
if (__DEV__) {
|
||||
return spyOn(...args);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => (numExpectations = 0));
|
||||
|
||||
jasmine.currentEnv_.addReporter({
|
||||
|
||||
@@ -6,12 +6,17 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
|
||||
require('./setupSpecEquivalenceReporter.js');
|
||||
} else {
|
||||
var env = jasmine.getEnv();
|
||||
var errorMap = require('../error-codes/codes.json');
|
||||
|
||||
// TODO: Stop using spyOn in all the test since that seem deprecated.
|
||||
// This is a legacy upgrade path strategy from:
|
||||
// https://github.com/facebook/jest/blob/v20.0.4/packages/jest-matchers/src/spyMatchers.js#L160
|
||||
const isSpy = spy => spy.calls && typeof spy.calls.count === 'function';
|
||||
|
||||
// Dev-only spyOn should be ignored in production runs.
|
||||
global.spyOnDev =
|
||||
process.env.NODE_ENV === 'production' ? () => {} : global.spyOn;
|
||||
|
||||
['error', 'warn'].forEach(methodName => {
|
||||
var oldMethod = console[methodName];
|
||||
var newMethod = function() {
|
||||
@@ -46,28 +51,39 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
|
||||
});
|
||||
});
|
||||
|
||||
var wrapDevMatcher = function(obj, name) {
|
||||
const original = obj[name];
|
||||
obj[name] = function devMatcher() {
|
||||
try {
|
||||
original.apply(this, arguments);
|
||||
} catch (e) {
|
||||
global.__hadDevFailures = e.stack;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// In production, we strip error messages and turn them into codes.
|
||||
// This decodes them back so that the test assertions on them work.
|
||||
var decodeErrorMessage = function(message) {
|
||||
if (!message) {
|
||||
return message;
|
||||
}
|
||||
const re = /error-decoder.html\?invariant=(\d+)([^\s]*)/;
|
||||
const matches = message.match(re);
|
||||
if (!matches || matches.length !== 3) {
|
||||
return message;
|
||||
}
|
||||
const code = parseInt(matches[1], 10);
|
||||
const args = matches[2]
|
||||
.split('&')
|
||||
.filter(s => s.startsWith('args[]='))
|
||||
.map(s => s.substr('args[]='.length))
|
||||
.map(decodeURIComponent);
|
||||
const format = errorMap[code];
|
||||
let argIndex = 0;
|
||||
return format.replace(/%s/g, () => args[argIndex++]);
|
||||
};
|
||||
};
|
||||
|
||||
const expectDev = function expectDev(actual) {
|
||||
const expectation = expect(actual);
|
||||
if (global.__suppressDevFailures) {
|
||||
Object.keys(expectation).forEach(name => {
|
||||
wrapDevMatcher(expectation, name);
|
||||
wrapDevMatcher(expectation.not, name);
|
||||
});
|
||||
}
|
||||
return expectation;
|
||||
};
|
||||
global.expectDev = expectDev;
|
||||
const OriginalError = global.Error;
|
||||
const ErrorProxy = new Proxy(OriginalError, {
|
||||
construct(target, argumentsList, newTarget) {
|
||||
const error = Reflect.construct(target, argumentsList, newTarget);
|
||||
error.message = decodeErrorMessage(error.message);
|
||||
return error;
|
||||
},
|
||||
});
|
||||
ErrorProxy.OriginalError = OriginalError;
|
||||
global.Error = ErrorProxy;
|
||||
}
|
||||
|
||||
require('jasmine-check').install();
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
|
||||
console.log('Running Jest');
|
||||
|
||||
const args = [
|
||||
path.join('node_modules', 'jest-cli', 'bin', 'jest'),
|
||||
'--runInBand',
|
||||
];
|
||||
if (argv.coverage) {
|
||||
args.push('--coverage');
|
||||
}
|
||||
|
||||
const jest = spawn('node', args, {
|
||||
stdio: 'inherit',
|
||||
env: Object.assign({}, process.env, {
|
||||
NODE_ENV: 'test',
|
||||
}),
|
||||
});
|
||||
|
||||
jest.on('close', code => {
|
||||
if (code === 1) {
|
||||
console.error('Jest failed!');
|
||||
} else {
|
||||
console.log('Jest passed!');
|
||||
}
|
||||
process.exit(code);
|
||||
});
|
||||
@@ -1270,6 +1270,13 @@ create-react-class@^15.6.2:
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
cross-env@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.1.tgz#b6d8ab97f304c0f71dae7277b75fe424c08dfa74"
|
||||
dependencies:
|
||||
cross-spawn "^5.1.0"
|
||||
is-windows "^1.0.0"
|
||||
|
||||
cross-spawn-async@^2.2.2:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc"
|
||||
@@ -1284,7 +1291,7 @@ cross-spawn@^2.1.5:
|
||||
cross-spawn-async "^2.2.2"
|
||||
spawn-sync "^1.0.15"
|
||||
|
||||
cross-spawn@^5.0.1:
|
||||
cross-spawn@^5.0.1, cross-spawn@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
|
||||
dependencies:
|
||||
@@ -2592,6 +2599,10 @@ is-windows@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
|
||||
|
||||
is-windows@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9"
|
||||
|
||||
[email protected]:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
|
||||
Reference in New Issue
Block a user