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