diff --git a/.eslintignore b/.eslintignore
index 4eaf46c6d7d..27c694cea55 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -3,3 +3,4 @@ build
my-app*
packages/react-scripts/template
packages/react-scripts/fixtures
+fixtures/
diff --git a/.travis.yml b/.travis.yml
index 0608fe72253..fd83ef7393e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,23 +7,25 @@ node_js:
cache:
yarn: true
directories:
- - .npm
+ - .npm
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash
- export PATH="$HOME/.yarn/bin:$PATH"
install: true
script:
- - 'if [ $TEST_SUITE = "simple" ]; then tasks/e2e-simple.sh; fi'
- - 'if [ $TEST_SUITE = "installs" ]; then tasks/e2e-installs.sh; fi'
- - 'if [ $TEST_SUITE = "kitchensink" ]; then tasks/e2e-kitchensink.sh; fi'
- - 'if [ $TEST_SUITE = "kitchensink-eject" ]; then tasks/e2e-kitchensink-eject.sh; fi'
- - 'if [ $TEST_SUITE = "old-node" ]; then tasks/e2e-old-node.sh; fi'
+ - 'if [ $TEST_SUITE = "simple" ]; then tasks/e2e-simple.sh; fi'
+ - 'if [ $TEST_SUITE = "installs" ]; then tasks/e2e-installs.sh; fi'
+ - 'if [ $TEST_SUITE = "kitchensink" ]; then tasks/e2e-kitchensink.sh; fi'
+ - 'if [ $TEST_SUITE = "kitchensink-eject" ]; then tasks/e2e-kitchensink-eject.sh; fi'
+ - 'if [ $TEST_SUITE = "old-node" ]; then tasks/e2e-old-node.sh; fi'
+ - 'if [ $TEST_SUITE = "behavior" ]; then tasks/e2e-behavior.sh; fi'
env:
matrix:
- TEST_SUITE=simple
- TEST_SUITE=installs
- TEST_SUITE=kitchensink
- TEST_SUITE=kitchensink-eject
+ - TEST_SUITE=behavior
matrix:
include:
- node_js: 4
diff --git a/fixtures/behavior/builds-with-multiple-runtimes/package.json b/fixtures/behavior/builds-with-multiple-runtimes/package.json
new file mode 100644
index 00000000000..a8cfdd29cce
--- /dev/null
+++ b/fixtures/behavior/builds-with-multiple-runtimes/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "builds-with-multiple-runtimes",
+ "description": "Tests that a build succeeds with multiple runtime versions",
+ "dependencies": {
+ "dva": "2.4.0",
+ "ky": "0.3.0"
+ }
+}
diff --git a/fixtures/behavior/builds-with-multiple-runtimes/src/index.js b/fixtures/behavior/builds-with-multiple-runtimes/src/index.js
new file mode 100644
index 00000000000..b0603469a80
--- /dev/null
+++ b/fixtures/behavior/builds-with-multiple-runtimes/src/index.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import dva from 'dva';
+import createHistory from 'history/createHashHistory';
+import ky from 'ky';
+
+const app = dva({ history: createHistory() });
+app.router(() => {
+ ky.get('https://canihazip.com/s')
+ .then(r => r.text())
+ .then(console.log, console.error)
+ .then(() => console.log('ok'));
+ return
Test
;
+});
+app.start('#root');
diff --git a/package.json b/package.json
index 8e57e6d6a62..a58e2a67d06 100644
--- a/package.json
+++ b/package.json
@@ -18,8 +18,10 @@
"format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'"
},
"devDependencies": {
+ "cross-spawn": "^6.0.5",
"eslint": "5.6.0",
"execa": "1.0.0",
+ "fs-extra": "^7.0.0",
"husky": "1.0.0-rc.15",
"lerna": "2.9.1",
"lerna-changelog": "^0.8.0",
diff --git a/tasks/e2e-behavior.sh b/tasks/e2e-behavior.sh
new file mode 100755
index 00000000000..fe34514333f
--- /dev/null
+++ b/tasks/e2e-behavior.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+# Copyright (c) 2015-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.
+
+# ******************************************************************************
+# This is an end-to-end kitchensink test intended to run on CI.
+# You can also run it locally but it's slow.
+# ******************************************************************************
+
+# Start in tasks/ even if run from root directory
+cd "$(dirname "$0")"
+
+# CLI, app, and test module temporary locations
+# http://unix.stackexchange.com/a/84980
+temp_app_path=`mktemp -d 2>/dev/null || mktemp -d -t 'temp_app_path'`
+temp_module_path=`mktemp -d 2>/dev/null || mktemp -d -t 'temp_module_path'`
+custom_registry_url=http://localhost:4873
+original_npm_registry_url=`npm get registry`
+original_yarn_registry_url=`yarn config get registry`
+
+function cleanup {
+ echo 'Cleaning up.'
+ ps -ef | grep 'react-scripts' | grep -v grep | awk '{print $2}' | xargs kill -9
+ cd "$root_path"
+ npm set registry "$original_npm_registry_url"
+ yarn config set registry "$original_yarn_registry_url"
+}
+
+# Error messages are redirected to stderr
+function handle_error {
+ echo "$(basename $0): ERROR! An error was encountered executing line $1." 1>&2;
+ cleanup
+ echo 'Exiting with error.' 1>&2;
+ exit 1
+}
+
+function handle_exit {
+ cleanup
+ echo 'Exiting without error.' 1>&2;
+ exit
+}
+
+# Check for the existence of one or more files.
+function exists {
+ for f in $*; do
+ test -e "$f"
+ done
+}
+
+# Exit the script with a helpful error message when any error is encountered
+trap 'set +x; handle_error $LINENO $BASH_COMMAND' ERR
+
+# Cleanup before exit on any termination signal
+trap 'set +x; handle_exit' SIGQUIT SIGTERM SIGINT SIGKILL SIGHUP
+
+# Echo every command being executed
+set -x
+
+# Go to root
+cd ..
+root_path=$PWD
+
+if hash npm 2>/dev/null
+then
+ npm i -g npm@latest
+fi
+
+# Bootstrap monorepo
+yarn
+
+# ******************************************************************************
+# First, publish the monorepo.
+# ******************************************************************************
+
+# Start local registry
+tmp_registry_log=`mktemp`
+nohup npx verdaccio@3.2.0 -c tasks/verdaccio.yaml &>$tmp_registry_log &
+# Wait for `verdaccio` to boot
+grep -q 'http address' <(tail -f $tmp_registry_log)
+
+# Set registry to local registry
+npm set registry "$custom_registry_url"
+yarn config set registry "$custom_registry_url"
+
+# Login so we can publish packages
+(cd && npx npm-auth-to-token@1.0.0 -u user -p password -e user@example.com -r "$custom_registry_url")
+
+# Publish the monorepo
+git clean -df
+./tasks/publish.sh --yes --force-publish=* --skip-git --cd-version=prerelease --exact --npm-tag=latest
+
+# ******************************************************************************
+# Now that we have published them, create a clean app folder and install them.
+# ******************************************************************************
+
+# Install the app in a temporary location
+cd $temp_app_path
+npx create-react-app test-behavior
+
+# ******************************************************************************
+# Now that we used create-react-app to create an app depending on react-scripts,
+# let's run through all of our behavior tests.
+# ******************************************************************************
+
+# Enter the app directory
+cd "$temp_app_path/test-behavior"
+
+node "$root_path"/tasks/test-behavior.js "$temp_app_path/test-behavior"
+
+# Cleanup
+cleanup
diff --git a/tasks/test-behavior.js b/tasks/test-behavior.js
new file mode 100644
index 00000000000..46bba908a0e
--- /dev/null
+++ b/tasks/test-behavior.js
@@ -0,0 +1,97 @@
+'use strict';
+
+const args = process.argv.slice(2);
+const fs = require('fs-extra');
+const path = require('path');
+const os = require('os');
+const spawn = require('cross-spawn');
+
+const applicationPath = args.pop();
+const applicationPackageJson = path.resolve(applicationPath, 'package.json');
+const applicationSrc = path.resolve(applicationPath, 'src');
+const applicationModules = path.resolve(applicationPath, 'node_modules');
+
+const fixturePath = path.resolve(__dirname, '..', 'fixtures', 'behavior');
+const fixtures = fs
+ .readdirSync(fixturePath)
+ .map(fixture => path.resolve(fixturePath, fixture))
+ .filter(path => fs.lstatSync(path).isDirectory);
+
+const packageContents = require(applicationPackageJson);
+
+function install({ root }) {
+ spawn.sync('yarnpkg', ['--cwd', root, 'install'], { cwd: root });
+}
+
+function startApp({ root }) {
+ const output = spawn
+ .sync('yarnpkg', ['start', '--smoke-test'], { cwd: root })
+ .output.join('');
+
+ if (!/Compiled successfully/.test(output)) {
+ throw new Error(output);
+ }
+
+ console.log('\t = application started: ', output);
+}
+
+function buildApp({ root }) {
+ const output = spawn
+ .sync('yarnpkg', ['build'], { cwd: root })
+ .output.join('');
+
+ if (!/Compiled successfully/.test(output)) {
+ throw new Error(output);
+ }
+
+ console.log('\t = application built: ', output);
+}
+
+console.log(`=> checking ${fixtures.length} fixtures`);
+for (const fixture of fixtures) {
+ const {
+ name,
+ description,
+ dependencies,
+ devDependencies,
+ } = require(path.resolve(fixture, 'package.json'));
+ console.log(`\t * checking fixture ${name}`);
+ console.log(`\t ... this fixture: ${description}`);
+
+ fs.emptyDirSync(applicationSrc);
+ fs.emptyDirSync(applicationModules);
+ fs.copySync(path.resolve(fixture, 'src'), applicationSrc);
+
+ try {
+ fs.writeJsonSync(
+ applicationPackageJson,
+ Object.assign({}, packageContents, {
+ dependencies: Object.assign(
+ {},
+ packageContents.dependencies,
+ dependencies
+ ),
+ devDependencies: Object.assign(
+ {},
+ packageContents.devDependencies,
+ devDependencies
+ ),
+ }),
+ {
+ spaces: 2,
+ EOL: os.EOL,
+ }
+ );
+ install({ root: applicationPath });
+ startApp({ root: applicationPath });
+ buildApp({ root: applicationPath });
+ } catch (e) {
+ console.error(`\t ! failed on ${name}:`);
+ throw e;
+ } finally {
+ fs.writeJsonSync(applicationPackageJson, packageContents, {
+ spaces: 2,
+ EOL: os.EOL,
+ });
+ }
+}