Commit 9099570b authored by Fabrizio Castellarin's avatar Fabrizio Castellarin Committed by Dan Abramov
Browse files

Use a more sophisticated template for end-to-end testing. (#1187)

* Use a more sophisticated template for end-to-end testing.

* Not publish integration tests to npm

* Use "commander" for  cli argv handling

* Handle different scripts version forms and exits without a name given

* Prepare the commands for testing with a template

* Fix dev "template" path

* Add various features to test

* Test various features separately

* Test language features

* Comment unused e2e.sh lines

* Add "development" tests

* Test environment variables

* Test webpack plugins

* Replace kitchensink README

* Switch integration tests from jest to mocha

* Use `fs-extra`

* Use the correct folders

* Do some cleanup

* Print a better message for `--template`

* Test `npm start` with and without https

* Separate fast e2e testing from kitchensink testing

* Hide `--internal-testing-template` (former `--template`) CLI option
parent 7cd03f9f
Showing with 439 additions and 12 deletions
+439 -12
--- ---
language: node_js language: node_js
node_js: node_js:
- 0.10
- 4 - 4
- 6 - 6
cache: cache:
...@@ -9,7 +8,20 @@ cache: ...@@ -9,7 +8,20 @@ cache:
- node_modules - node_modules
- packages/create-react-app/node_modules - packages/create-react-app/node_modules
- packages/react-scripts/node_modules - packages/react-scripts/node_modules
script: tasks/e2e.sh 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'
env: env:
- USE_YARN=no global:
- USE_YARN=yes - USE_YARN=no
matrix:
- TEST_SUITE=simple
- TEST_SUITE=installs
- TEST_SUITE=kitchensink
matrix:
include:
- node_js: 0.10
env: TEST_SUITE=simple
- node_js: 6
env: USE_YARN=yes TEST_SUITE=simple
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"build": "node packages/react-scripts/scripts/build.js", "build": "node packages/react-scripts/scripts/build.js",
"changelog": "lerna-changelog", "changelog": "lerna-changelog",
"create-react-app": "tasks/cra.sh", "create-react-app": "tasks/cra.sh",
"e2e": "tasks/e2e.sh", "e2e": "tasks/e2e-simple.sh",
"postinstall": "lerna bootstrap", "postinstall": "lerna bootstrap",
"publish": "tasks/release.sh", "publish": "tasks/release.sh",
"start": "node packages/react-scripts/scripts/start.js", "start": "node packages/react-scripts/scripts/start.js",
......
...@@ -52,6 +52,7 @@ if (currentNodeVersion.split('.')[0] < 4) { ...@@ -52,6 +52,7 @@ if (currentNodeVersion.split('.')[0] < 4) {
process.exit(1); process.exit(1);
} }
var commander = require('commander');
var fs = require('fs-extra'); var fs = require('fs-extra');
var path = require('path'); var path = require('path');
var execSync = require('child_process').execSync; var execSync = require('child_process').execSync;
...@@ -60,7 +61,7 @@ var semver = require('semver'); ...@@ -60,7 +61,7 @@ var semver = require('semver');
var projectName; var projectName;
var program = require('commander') var program = commander
.version(require('./package.json').version) .version(require('./package.json').version)
.arguments('<project-directory>') .arguments('<project-directory>')
.usage(chalk.green('<project-directory>') + ' [options]') .usage(chalk.green('<project-directory>') + ' [options]')
...@@ -69,6 +70,7 @@ var program = require('commander') ...@@ -69,6 +70,7 @@ var program = require('commander')
}) })
.option('--verbose', 'print additional logs') .option('--verbose', 'print additional logs')
.option('--scripts-version <alternative-package>', 'use a non-standard version of react-scripts') .option('--scripts-version <alternative-package>', 'use a non-standard version of react-scripts')
.allowUnknownOption()
.on('--help', function () { .on('--help', function () {
console.log(' Only ' + chalk.green('<project-directory>') + ' is required.'); console.log(' Only ' + chalk.green('<project-directory>') + ' is required.');
console.log(); console.log();
...@@ -82,7 +84,7 @@ var program = require('commander') ...@@ -82,7 +84,7 @@ var program = require('commander')
console.log(' ' + chalk.cyan('https://github.com/facebookincubator/create-react-app/issues/new')); console.log(' ' + chalk.cyan('https://github.com/facebookincubator/create-react-app/issues/new'));
console.log(); console.log();
}) })
.parse(process.argv) .parse(process.argv);
if (typeof projectName === 'undefined') { if (typeof projectName === 'undefined') {
console.error('Please specify the project directory:'); console.error('Please specify the project directory:');
...@@ -95,9 +97,14 @@ if (typeof projectName === 'undefined') { ...@@ -95,9 +97,14 @@ if (typeof projectName === 'undefined') {
process.exit(1); process.exit(1);
} }
createApp(projectName, program.verbose, program.scriptsVersion); var hiddenProgram = new commander.Command()
.option('--internal-testing-template <path-to-template>', '(internal usage only, DO NOT RELY ON THIS) ' +
'use a non-standard application template')
.parse(process.argv)
createApp(projectName, program.verbose, program.scriptsVersion, hiddenProgram.internalTestingTemplate);
function createApp(name, verbose, version) { function createApp(name, verbose, version, template) {
var root = path.resolve(name); var root = path.resolve(name);
var appName = path.basename(root); var appName = path.basename(root);
...@@ -130,7 +137,7 @@ function createApp(name, verbose, version) { ...@@ -130,7 +137,7 @@ function createApp(name, verbose, version) {
console.log('Installing ' + chalk.cyan('react-scripts') + '...'); console.log('Installing ' + chalk.cyan('react-scripts') + '...');
console.log(); console.log();
run(root, appName, version, verbose, originalDirectory); run(root, appName, version, verbose, originalDirectory, template);
} }
function shouldUseYarn() { function shouldUseYarn() {
...@@ -163,7 +170,7 @@ function install(packageToInstall, verbose, callback) { ...@@ -163,7 +170,7 @@ function install(packageToInstall, verbose, callback) {
}); });
} }
function run(root, appName, version, verbose, originalDirectory) { function run(root, appName, version, verbose, originalDirectory, template) {
var packageToInstall = getInstallPackage(version); var packageToInstall = getInstallPackage(version);
var packageName = getPackageName(packageToInstall); var packageName = getPackageName(packageToInstall);
...@@ -183,7 +190,7 @@ function run(root, appName, version, verbose, originalDirectory) { ...@@ -183,7 +190,7 @@ function run(root, appName, version, verbose, originalDirectory) {
'init.js' 'init.js'
); );
var init = require(scriptsPath); var init = require(scriptsPath);
init(root, appName, verbose, originalDirectory); init(root, appName, verbose, originalDirectory, template);
}); });
} }
......
/fixtures
{
"presets": ["latest"]
}
REACT_APP_FILE_ENV_MESSAGE=fromtheenvfile
[ignore]
<PROJECT_ROOT>/node_modules/fbjs/.*
[include]
[libs]
[options]
{
"dependencies": {
"babel-preset-latest": "6.16.0",
"babel-register": "6.18.0",
"babel-polyfill": "6.20.0",
"chai": "3.5.0",
"jsdom": "9.8.3",
"mocha": "3.2.0"
}
}
# See http://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# testing
coverage
# production
build
# misc
.DS_Store
.env
npm-debug.log
import { expect } from 'chai'
import initDOM from './initDOM'
describe('Integration', () => {
describe('Environment variables', () => {
it('NODE_PATH', async () => {
const doc = await initDOM('node-path')
expect(doc.getElementById('feature-node-path').childElementCount).to.equal(4)
})
it('shell env variables', async () => {
const doc = await initDOM('shell-env-variables')
expect(doc.getElementById('feature-shell-env-variables').textContent).to.equal('fromtheshell.')
})
it('file env variables', async () => {
const doc = await initDOM('file-env-variables')
expect(doc.getElementById('feature-file-env-variables').textContent).to.equal('fromtheenvfile.')
})
})
})
const fs = require('fs')
const http = require('http')
const jsdom = require('jsdom')
const path = require('path')
let getMarkup
let resourceLoader
// this value could be tweaked in order to let the resource
// retriever get every file and jsdom execute react
let timeToWaitForJsToExecute
if (process.env.E2E_FILE) {
const file = path.isAbsolute(process.env.E2E_FILE)
? process.env.E2E_FILE
: path.join(process.cwd(), process.env.E2E_FILE)
const markup = fs.readFileSync(file, 'utf8')
getMarkup = () => markup
resourceLoader = (resource, callback) => callback(
null,
fs.readFileSync(path.join(path.dirname(file), resource.url.pathname), 'utf8')
)
timeToWaitForJsToExecute = 0
} else if (process.env.E2E_URL) {
getMarkup = () => new Promise(resolve => {
http.get(process.env.E2E_URL, (res) => {
let rawData = ''
res.on('data', chunk => rawData += chunk)
res.on('end', () => resolve(rawData))
})
})
resourceLoader = (resource, callback) => {
return resource.defaultFetch(callback)
}
timeToWaitForJsToExecute = 100
} else {
it.only('can run jsdom (at least one of "E2E_FILE" or "E2E_URL" environment variables must be provided)', () => {
expect(new Error('This isn\'t the error you are looking for.')).toBeUndefined()
})
}
export default feature => new Promise(async resolve => {
const markup = await getMarkup()
const host = process.env.E2E_URL || 'http://localhost:3000'
const doc = jsdom.jsdom(markup, {
features : {
FetchExternalResources : ['script', 'css'],
ProcessExternalResources : ['script'],
},
resourceLoader,
url: `${host}#${feature}`,
virtualConsole: jsdom.createVirtualConsole().sendTo(console),
})
doc.defaultView.addEventListener('load', () => {
setTimeout(() => resolve(doc), timeToWaitForJsToExecute)
}, false)
})
import { expect } from 'chai'
import initDOM from './initDOM'
describe('Integration', () => {
describe('Language syntax', () => {
it('array destructuring', async () => {
const doc = await initDOM('array-destructuring')
expect(doc.getElementById('feature-array-destructuring').childElementCount).to.equal(4)
})
it('array spread', async () => {
const doc = await initDOM('array-spread')
expect(doc.getElementById('feature-array-spread').childElementCount).to.equal(4)
})
it('async/await', async () => {
const doc = await initDOM('async-await')
expect(doc.getElementById('feature-async-await').childElementCount).to.equal(4)
})
it('class properties', async () => {
const doc = await initDOM('class-properties')
expect(doc.getElementById('feature-class-properties').childElementCount).to.equal(4)
})
it('computed properties', async () => {
const doc = await initDOM('computed-properties')
expect(doc.getElementById('feature-computed-properties').childElementCount).to.equal(4)
})
it('custom interpolation', async () => {
const doc = await initDOM('custom-interpolation')
expect(doc.getElementById('feature-custom-interpolation').childElementCount).to.equal(4)
})
it('default parameters', async () => {
const doc = await initDOM('default-parameters')
expect(doc.getElementById('feature-default-parameters').childElementCount).to.equal(4)
})
it('destructuring and await', async () => {
const doc = await initDOM('destructuring-and-await')
expect(doc.getElementById('feature-destructuring-and-await').childElementCount).to.equal(4)
})
it('generators', async () => {
const doc = await initDOM('generators')
expect(doc.getElementById('feature-generators').childElementCount).to.equal(4)
})
it('object destructuring', async () => {
const doc = await initDOM('object-destructuring')
expect(doc.getElementById('feature-object-destructuring').childElementCount).to.equal(4)
})
it('object spread', async () => {
const doc = await initDOM('object-spread')
expect(doc.getElementById('feature-object-spread').childElementCount).to.equal(4)
})
it('promises', async () => {
const doc = await initDOM('promises')
expect(doc.getElementById('feature-promises').childElementCount).to.equal(4)
})
it('rest + default', async () => {
const doc = await initDOM('rest-and-default')
expect(doc.getElementById('feature-rest-and-default').childElementCount).to.equal(4)
})
it('rest parameters', async () => {
const doc = await initDOM('rest-parameters')
expect(doc.getElementById('feature-rest-parameters').childElementCount).to.equal(4)
})
it('template interpolation', async () => {
const doc = await initDOM('template-interpolation')
expect(doc.getElementById('feature-template-interpolation').childElementCount).to.equal(4)
})
})
})
import { expect } from 'chai'
import initDOM from './initDOM'
describe('Integration', () => {
describe('Webpack plugins', () => {
it('css inclusion', async () => {
const doc = await initDOM('css-inclusion')
expect(doc.getElementsByTagName('style')[0].textContent.replace(/\s/g, ''))
.to.match(/#feature-css-inclusion\{background:.+;color:.+}/)
})
it('image inclusion', async () => {
const doc = await initDOM('image-inclusion')
expect(doc.getElementById('feature-image-inclusion').src).to.match(/^data:image\/jpeg;base64.+==$/)
})
it('no ext inclusion', async () => {
const doc = await initDOM('no-ext-inclusion')
expect(doc.getElementById('feature-no-ext-inclusion').textContent)
.to.equal('This is just a file without an extension.')
})
it('json inclusion', async () => {
const doc = await initDOM('json-inclusion')
expect(doc.getElementById('feature-json-inclusion').textContent).to.equal('This is an abstract.')
})
it('svg inclusion', async () => {
const doc = await initDOM('svg-inclusion')
expect(doc.getElementById('feature-svg-inclusion').src).to.match(/\/static\/media\/logo\..+\.svg$/)
})
it('unknown ext inclusion', async () => {
const doc = await initDOM('unknown-ext-inclusion')
expect(doc.getElementById('feature-unknown-ext-inclusion').textContent).to.equal('Whoooo, spooky!.')
})
})
})
packages/react-scripts/fixtures/kitchensink/public/favicon.ico

24.3 KB

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = { feature: null };
this.setFeature = this.setFeature.bind(this);
}
componentDidMount() {
switch (location.hash.slice(1)) {
case 'array-destructuring':
require.ensure([], () => this.setFeature(require('./features/syntax/ArrayDestructuring').default));
break;
case 'array-spread':
require.ensure([], () => this.setFeature(require('./features/syntax/ArraySpread').default));
break;
case 'async-await':
require.ensure([], () => this.setFeature(require('./features/syntax/AsyncAwait').default));
break;
case 'class-properties':
require.ensure([], () => this.setFeature(require('./features/syntax/ClassProperties').default));
break;
case 'computed-properties':
require.ensure([], () => this.setFeature(require('./features/syntax/ComputedProperties').default));
break;
case 'css-inclusion':
require.ensure([], () => this.setFeature(require('./features/webpack/CssInclusion').default));
break;
case 'custom-interpolation':
require.ensure([], () => this.setFeature(require('./features/syntax/CustomInterpolation').default));
break;
case 'default-parameters':
require.ensure([], () => this.setFeature(require('./features/syntax/DefaultParameters').default));
break;
case 'destructuring-and-await':
require.ensure([], () => this.setFeature(require('./features/syntax/DestructuringAndAwait').default));
break;
case 'file-env-variables':
require.ensure([], () => this.setFeature(require('./features/env/FileEnvVariables').default));
break;
case 'generators':
require.ensure([], () => this.setFeature(require('./features/syntax/Generators').default));
break;
case 'image-inclusion':
require.ensure([], () => this.setFeature(require('./features/webpack/ImageInclusion').default));
break;
case 'json-inclusion':
require.ensure([], () => this.setFeature(require('./features/webpack/JsonInclusion').default));
break;
case 'node-path':
require.ensure([], () => this.setFeature(require('./features/env/NodePath').default));
break;
case 'no-ext-inclusion':
require.ensure([], () => this.setFeature(require('./features/webpack/NoExtInclusion').default));
break;
case 'object-destructuring':
require.ensure([], () => this.setFeature(require('./features/syntax/ObjectDestructuring').default));
break;
case 'object-spread':
require.ensure([], () => this.setFeature(require('./features/syntax/ObjectSpread').default));
break;
case 'promises':
require.ensure([], () => this.setFeature(require('./features/syntax/Promises').default));
break;
case 'rest-and-default':
require.ensure([], () => this.setFeature(require('./features/syntax/RestAndDefault').default));
break;
case 'rest-parameters':
require.ensure([], () => this.setFeature(require('./features/syntax/RestParameters').default));
break;
case 'shell-env-variables':
require.ensure([], () => this.setFeature(require('./features/env/ShellEnvVariables').default));
break;
case 'svg-inclusion':
require.ensure([], () => this.setFeature(require('./features/webpack/SvgInclusion').default));
break;
case 'template-interpolation':
require.ensure([], () => this.setFeature(require('./features/syntax/TemplateInterpolation').default));
break;
case 'unknown-ext-inclusion':
require.ensure([], () => this.setFeature(require('./features/webpack/UnknownExtInclusion').default)
);
break;
default:
this.setFeature(null);
break;
}
}
setFeature(feature) {
this.setState({ feature });
}
render() {
const Feature = this.state.feature;
return Feature ? <Feature /> : null;
}
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});
export default () => [
{ id: 1, name: '1' },
{ id: 2, name: '2' },
{ id: 3, name: '3' },
{ id: 4, name: '4' }
]
import React from 'react'
export default () => (
<span id="feature-file-env-variables">{process.env.REACT_APP_FILE_ENV_MESSAGE}.</span>
)
import React from 'react';
import ReactDOM from 'react-dom';
import FileEnvVariables from './FileEnvVariables';
describe('.env variables', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<FileEnvVariables />, div);
});
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment