diff --git a/package.json b/package.json index 65085e7a08b23..c8ce7b99bce5a 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "babel-plugin-transform-es3-property-literals": "^6.5.0", "babel-plugin-transform-object-rest-spread": "^6.6.5", "babel-plugin-transform-react-jsx-source": "^6.8.0", + "babel-plugin-transform-regenerator": "^6.26.0", "babel-preset-react": "^6.5.0", "babel-traverse": "^6.9.0", "babylon": "6.15.0", @@ -103,40 +104,14 @@ "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": "cross-env NODE_ENV=development jest", - "test-prod": "cross-env NODE_ENV=production jest", + "test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js", + "test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js", + "test-prod-build": "yarn test-build-prod", + "test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js", + "test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js", "flow": "node ./scripts/tasks/flow.js", "prettier": "node ./scripts/prettier/index.js write-changed", "prettier-all": "node ./scripts/prettier/index.js write", "version-check": "node ./scripts/tasks/version-check.js" - }, - "jest": { - "modulePathIgnorePatterns": [ - "/scripts/rollup/shims/", - "/scripts/bench/" - ], - "transform": { - ".*": "./scripts/jest/preprocessor.js" - }, - "setupFiles": [ - "./scripts/jest/environment.js" - ], - "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", - "testRegex": "/__tests__/.*(\\.js|\\.coffee|[^d]\\.ts)$", - "moduleFileExtensions": [ - "js", - "json", - "node", - "coffee", - "ts" - ], - "roots": [ - "/packages", - "/scripts" - ], - "collectCoverageFrom": [ - "packages/**/*.js" - ], - "timers": "fake" } } diff --git a/packages/events/__tests__/EventPluginRegistry-test.js b/packages/events/__tests__/EventPluginRegistry-test.internal.js similarity index 100% rename from packages/events/__tests__/EventPluginRegistry-test.js rename to packages/events/__tests__/EventPluginRegistry-test.internal.js diff --git a/packages/events/__tests__/ResponderEventPlugin-test.js b/packages/events/__tests__/ResponderEventPlugin-test.internal.js similarity index 100% rename from packages/events/__tests__/ResponderEventPlugin-test.js rename to packages/events/__tests__/ResponderEventPlugin-test.internal.js diff --git a/packages/events/__tests__/accumulateInto-test.js b/packages/events/__tests__/accumulateInto-test.internal.js similarity index 100% rename from packages/events/__tests__/accumulateInto-test.js rename to packages/events/__tests__/accumulateInto-test.internal.js diff --git a/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.js b/packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js similarity index 100% rename from packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.js rename to packages/react-cs-renderer/src/__tests__/ReactNativeCS-test.internal.js diff --git a/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js b/packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.internal.js similarity index 100% rename from packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js rename to packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.internal.js diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js index 2c40743854c36..4414dd37e33ae 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js @@ -1709,15 +1709,25 @@ describe('ReactDOMComponent', () => { it('should warn about incorrect casing on event handlers (ssr)', () => { spyOnDev(console, 'error'); ReactDOMServer.renderToString( - React.createElement('input', {type: 'text', onclick: '1'}), + React.createElement('input', {type: 'text', oninput: '1'}), ); ReactDOMServer.renderToString( React.createElement('input', {type: 'text', onKeydown: '1'}), ); if (__DEV__) { - expect(console.error.calls.count()).toBe(2); - expect(console.error.calls.argsFor(0)[0]).toContain('onClick'); - expect(console.error.calls.argsFor(1)[0]).toContain('onKeyDown'); + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Invalid event handler property `oninput`. ' + + 'React events use the camelCase naming convention, ' + + // Note: we don't know the right event name so we + // use a generic one (onClick) as a suggestion. + // This is because we don't bundle the event system + // on the server. + 'for example `onClick`.', + ); + // We can't warn for `onKeydown` on the server because + // there is no way tell if this is a valid event or not + // without access to the event system (which we don't bundle). } }); @@ -1735,14 +1745,14 @@ describe('ReactDOMComponent', () => { it('should warn about incorrect casing on event handlers', () => { spyOnDev(console, 'error'); ReactTestUtils.renderIntoDocument( - React.createElement('input', {type: 'text', onclick: '1'}), + React.createElement('input', {type: 'text', oninput: '1'}), ); ReactTestUtils.renderIntoDocument( React.createElement('input', {type: 'text', onKeydown: '1'}), ); if (__DEV__) { expect(console.error.calls.count()).toBe(2); - expect(console.error.calls.argsFor(0)[0]).toContain('onClick'); + expect(console.error.calls.argsFor(0)[0]).toContain('onInput'); expect(console.error.calls.argsFor(1)[0]).toContain('onKeyDown'); } }); @@ -1860,15 +1870,20 @@ describe('ReactDOMComponent', () => { it('gives source code refs for unknown prop warning (ssr)', () => { spyOnDev(console, 'error'); ReactDOMServer.renderToString(
); - ReactDOMServer.renderToString(); + ReactDOMServer.renderToString(); if (__DEV__) { expect(console.error.calls.count()).toBe(2); expect(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( 'Warning: Invalid DOM property `class`. Did you mean `className`?\n in div (at **)', ); expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: Invalid event handler property `onclick`. Did you mean ' + - '`onClick`?\n in input (at **)', + 'Warning: Invalid event handler property `oninput`. ' + + // Note: we don't know the right event name so we + // use a generic one (onClick) as a suggestion. + // This is because we don't bundle the event system + // on the server. + 'React events use the camelCase naming convention, for example `onClick`.' + + '\n in input (at **)', ); } }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js similarity index 100% rename from packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js rename to packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.internal.js similarity index 100% rename from packages/react-dom/src/__tests__/ReactDOMRoot-test.js rename to packages/react-dom/src/__tests__/ReactDOMRoot-test.internal.js diff --git a/packages/react-dom/src/__tests__/ReactDOMSelection-test.js b/packages/react-dom/src/__tests__/ReactDOMSelection-test.internal.js similarity index 100% rename from packages/react-dom/src/__tests__/ReactDOMSelection-test.js rename to packages/react-dom/src/__tests__/ReactDOMSelection-test.internal.js diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegration-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegration-test.js deleted file mode 100644 index efc2e7e514bdc..0000000000000 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegration-test.js +++ /dev/null @@ -1,3150 +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'; - -let ExecutionEnvironment; -let PropTypes; -let React; -let ReactDOM; -let ReactDOMServer; -let ReactTestUtils; - -const stream = require('stream'); - -const TEXT_NODE_TYPE = 3; - -// Helper functions for rendering tests -// ==================================== - -// promisified version of ReactDOM.render() -function asyncReactDOMRender(reactElement, domElement, forceHydrate) { - return new Promise(resolve => { - if (forceHydrate) { - ReactDOM.hydrate(reactElement, domElement); - } else { - ReactDOM.render(reactElement, domElement); - } - // We can't use the callback for resolution because that will not catch - // errors. They're thrown. - resolve(); - }); -} -// performs fn asynchronously and expects count errors logged to console.error. -// will fail the test if the count of errors logged is not equal to count. -async function expectErrors(fn, count) { - if (console.error.calls && console.error.calls.reset) { - console.error.calls.reset(); - } else { - spyOnDev(console, 'error'); - } - - const result = await fn(); - if ( - console.error.calls && - console.error.calls.count() !== count && - console.error.calls.count() !== 0 - ) { - console.log( - `We expected ${ - count - } warning(s), but saw ${console.error.calls.count()} warning(s).`, - ); - if (console.error.calls.count() > 0) { - console.log(`We saw these warnings:`); - for (var i = 0; i < console.error.calls.count(); i++) { - console.log(console.error.calls.argsFor(i)[0]); - } - } - } - if (__DEV__) { - expect(console.error.calls.count()).toBe(count); - } - return result; -} - -// renders the reactElement into domElement, and expects a certain number of errors. -// returns a Promise that resolves when the render is complete. -function renderIntoDom(reactElement, domElement, forceHydrate, errorCount = 0) { - return expectErrors(async () => { - ExecutionEnvironment.canUseDOM = true; - await asyncReactDOMRender(reactElement, domElement, forceHydrate); - ExecutionEnvironment.canUseDOM = false; - return domElement.firstChild; - }, errorCount); -} - -async function renderIntoString(reactElement, errorCount = 0) { - return await expectErrors( - () => - new Promise(resolve => - resolve(ReactDOMServer.renderToString(reactElement)), - ), - errorCount, - ); -} - -// Renders text using SSR and then stuffs it into a DOM node; returns the DOM -// element that corresponds with the reactElement. -// Does not render on client or perform client-side revival. -async function serverRender(reactElement, errorCount = 0) { - const markup = await renderIntoString(reactElement, errorCount); - var domElement = document.createElement('div'); - domElement.innerHTML = markup; - return domElement.firstChild; -} - -// this just drains a readable piped into it to a string, which can be accessed -// via .buffer. -class DrainWritable extends stream.Writable { - constructor(options) { - super(options); - this.buffer = ''; - } - - _write(chunk, encoding, cb) { - this.buffer += chunk; - cb(); - } -} - -async function renderIntoStream(reactElement, errorCount = 0) { - return await expectErrors( - () => - new Promise(resolve => { - let writable = new DrainWritable(); - ReactDOMServer.renderToNodeStream(reactElement).pipe(writable); - writable.on('finish', () => resolve(writable.buffer)); - }), - errorCount, - ); -} - -// Renders text using node stream SSR and then stuffs it into a DOM node; -// returns the DOM element that corresponds with the reactElement. -// Does not render on client or perform client-side revival. -async function streamRender(reactElement, errorCount = 0) { - const markup = await renderIntoStream(reactElement, errorCount); - var domElement = document.createElement('div'); - domElement.innerHTML = markup; - return domElement.firstChild; -} - -const clientCleanRender = (element, errorCount = 0) => { - const div = document.createElement('div'); - return renderIntoDom(element, div, false, errorCount); -}; - -const clientRenderOnServerString = async (element, errorCount = 0) => { - const markup = await renderIntoString(element, errorCount); - resetModules(); - - var domElement = document.createElement('div'); - domElement.innerHTML = markup; - let serverNode = domElement.firstChild; - - const firstClientNode = await renderIntoDom( - element, - domElement, - true, - errorCount, - ); - let clientNode = firstClientNode; - - // Make sure all top level nodes match up - while (serverNode || clientNode) { - expect(serverNode != null).toBe(true); - expect(clientNode != null).toBe(true); - expect(clientNode.nodeType).toBe(serverNode.nodeType); - // Assert that the DOM element hasn't been replaced. - // Note that we cannot use expect(serverNode).toBe(clientNode) because - // of jest bug #1772. - expect(serverNode === clientNode).toBe(true); - serverNode = serverNode.nextSibling; - clientNode = clientNode.nextSibling; - } - return firstClientNode; -}; - -function BadMarkupExpected() {} - -const clientRenderOnBadMarkup = async (element, errorCount = 0) => { - // First we render the top of bad mark up. - var domElement = document.createElement('div'); - domElement.innerHTML = - '
'; - await renderIntoDom(element, domElement, true, errorCount + 1); - - // This gives us the resulting text content. - var hydratedTextContent = domElement.textContent; - - // Next we render the element into a clean DOM node client side. - const cleanDomElement = document.createElement('div'); - ExecutionEnvironment.canUseDOM = true; - await asyncReactDOMRender(element, cleanDomElement, true); - ExecutionEnvironment.canUseDOM = false; - // This gives us the expected text content. - const cleanTextContent = cleanDomElement.textContent; - - // The only guarantee is that text content has been patched up if needed. - expect(hydratedTextContent).toBe(cleanTextContent); - - // Abort any further expects. All bets are off at this point. - throw new BadMarkupExpected(); -}; - -// runs a DOM rendering test as four different tests, with four different rendering -// scenarios: -// -- render to string on server -// -- render on client without any server markup "clean client render" -// -- render on client on top of good server-generated string markup -// -- render on client on top of bad server-generated markup -// -// testFn is a test that has one arg, which is a render function. the render -// function takes in a ReactElement and an optional expected error count and -// returns a promise of a DOM Element. -// -// You should only perform tests that examine the DOM of the results of -// render; you should not depend on the interactivity of the returned DOM element, -// as that will not work in the server string scenario. -function itRenders(desc, testFn) { - it(`renders ${desc} with server string render`, () => testFn(serverRender)); - it(`renders ${desc} with server stream render`, () => testFn(streamRender)); - itClientRenders(desc, testFn); -} - -// run testFn in three different rendering scenarios: -// -- render on client without any server markup "clean client render" -// -- render on client on top of good server-generated string markup -// -- render on client on top of bad server-generated markup -// -// testFn is a test that has one arg, which is a render function. the render -// function takes in a ReactElement and an optional expected error count and -// returns a promise of a DOM Element. -// -// Since all of the renders in this function are on the client, you can test interactivity, -// unlike with itRenders. -function itClientRenders(desc, testFn) { - it(`renders ${desc} with clean client render`, () => - testFn(clientCleanRender)); - it(`renders ${desc} with client render on top of good server markup`, () => - testFn(clientRenderOnServerString)); - it(`renders ${ - desc - } with client render on top of bad server markup`, async () => { - try { - await testFn(clientRenderOnBadMarkup); - } catch (x) { - // We expect this to trigger the BadMarkupExpected rejection. - if (!(x instanceof BadMarkupExpected)) { - // If not, rethrow. - throw x; - } - } - }); -} - -function itThrows(desc, testFn, partialMessage) { - it(`throws ${desc}`, () => { - return testFn().then( - () => expect(false).toBe('The promise resolved and should not have.'), - err => { - expect(err).toBeInstanceOf(Error); - expect(err.message).toContain(partialMessage); - }, - ); - }); -} - -function itThrowsWhenRendering(desc, testFn, partialMessage) { - itThrows( - `when rendering ${desc} with server string render`, - () => testFn(serverRender), - partialMessage, - ); - itThrows( - `when rendering ${desc} with clean client render`, - () => testFn(clientCleanRender), - partialMessage, - ); - - // we subtract one from the warning count here because the throw means that it won't - // get the usual markup mismatch warning. - itThrows( - `when rendering ${desc} with client render on top of bad server markup`, - () => - testFn((element, warningCount = 0) => - clientRenderOnBadMarkup(element, warningCount - 1), - ), - partialMessage, - ); -} - -// renders serverElement to a string, sticks it into a DOM element, and then -// tries to render clientElement on top of it. shouldMatch is a boolean -// telling whether we should expect the markup to match or not. -async function testMarkupMatch(serverElement, clientElement, shouldMatch) { - const domElement = await serverRender(serverElement); - resetModules(); - return renderIntoDom( - clientElement, - domElement.parentNode, - true, - shouldMatch ? 0 : 1, - ); -} - -// expects that rendering clientElement on top of a server-rendered -// serverElement does NOT raise a markup mismatch warning. -function expectMarkupMatch(serverElement, clientElement) { - return testMarkupMatch(serverElement, clientElement, true); -} - -// expects that rendering clientElement on top of a server-rendered -// serverElement DOES raise a markup mismatch warning. -function expectMarkupMismatch(serverElement, clientElement) { - return testMarkupMatch(serverElement, clientElement, false); -} - -// When there is a test that renders on server and then on client and expects a logged -// error, you want to see the error show up both on server and client. Unfortunately, -// React refuses to issue the same error twice to avoid clogging up the console. -// To get around this, we must reload React modules in between server and client render. -function resetModules() { - // First, reset the modules to load the client renderer. - jest.resetModuleRegistry(); - - // TODO: can we express this test with only public API? - ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); - require('shared/ReactFeatureFlags').enableReactFragment = true; - - PropTypes = require('prop-types'); - React = require('react'); - ReactDOM = require('react-dom'); - ReactTestUtils = require('react-dom/test-utils'); - - // Now we reset the modules again to load the server renderer. - // Resetting is important because we want to avoid any shared state - // influencing the tests. - jest.resetModuleRegistry(); - require('shared/ReactFeatureFlags').enableReactFragment = true; - ReactDOMServer = require('react-dom/server'); -} - -describe('ReactDOMServerIntegration', () => { - beforeEach(() => { - resetModules(); - - ExecutionEnvironment.canUseDOM = false; - }); - - describe('basic rendering', function() { - itRenders('a blank div', async render => { - const e = await render(
); - expect(e.tagName).toBe('DIV'); - }); - - itRenders('a self-closing tag', async render => { - const e = await render(
); - expect(e.tagName).toBe('BR'); - }); - - itRenders('a self-closing tag as a child', async render => { - const e = await render( -
-
-
, - ); - expect(e.childNodes.length).toBe(1); - expect(e.firstChild.tagName).toBe('BR'); - }); - - itRenders('a string', async render => { - let e = await render('Hello'); - expect(e.nodeType).toBe(3); - expect(e.nodeValue).toMatch('Hello'); - }); - - itRenders('a number', async render => { - let e = await render(42); - expect(e.nodeType).toBe(3); - expect(e.nodeValue).toMatch('42'); - }); - - itRenders('an array with one child', async render => { - let e = await render([
text1
]); - let parent = e.parentNode; - expect(parent.childNodes[0].tagName).toBe('DIV'); - }); - - itRenders('an array with several children', async render => { - let Header = props => { - return

header

; - }; - let Footer = props => { - return [

footer

,

about

]; - }; - let e = await render([ -
text1
, - text2, -
, -