Clean up the behavior tests (#5365)

* Speed up installs with pnp

* Move to a better relative path test

* Continue work on new test organization

* Move mjs test to new enhanced tests

* Move over last legacy test

* Update behavior e2e script

* Add first iteration of instructions to test readme

* Add some more bad instructions

* Split test command into multiple lines

* Use two workers (do not run in band)

* Bump install timeout

* No global pollution

* Fix PnP support via standard run mechanism

* Prevent circular serve command

* Silent yarn execs

* Fix common commands by using a shell

* Run with npx instead of Yarn

* Remove unused scripts

* Wait for localhost and fix script booting with PnP

* Don't pnp locally because links will cause module resolution to whine

* lint-staged is annoying

* Strip Yarn out of execa runs

* Set default license if none specified

* Don't impose worker limit

* Disable pnp for webpack messages

* Add missing dependency

* Disable timeout and rely on Jest

* Only use 2 jest workers

* Add missing dependency
This commit is contained in:
Joe Haddad
2018-10-10 11:05:15 -04:00
committed by GitHub
parent 04735de777
commit 5fecfee237
70 changed files with 547 additions and 584 deletions

View File

@@ -1,5 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`graphql with mjs entrypoint correctly bundles files in development 1`] = `"Pikachu"`;
exports[`graphql with mjs entrypoint correctly bundles files in production 1`] = `"Pikachu"`;

View File

@@ -1,56 +0,0 @@
const {
bootstrap,
startDevelopmentServer,
startProductionServer,
} = require('../../utils');
const puppeteer = require('puppeteer');
beforeEach(async () => {
await bootstrap({ directory: global.testDirectory, template: __dirname });
global.appDevPort = await startDevelopmentServer({
directory: global.testDirectory,
});
global.appProdPort = await startProductionServer({
directory: global.testDirectory,
});
// Wait for serve to boot up
await new Promise(resolve => setTimeout(resolve, 1000));
});
// https://github.com/facebook/create-react-app/issues/5234
// https://github.com/facebook/create-react-app/pull/5258
describe('graphql with mjs entrypoint', () => {
it('correctly bundles files in development', async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(`http://localhost:${global.appDevPort}/`);
await page.waitForSelector('.Pokemon-Name-Data');
const output = await page.evaluate(() => {
return Array.from(
document.getElementsByClassName('Pokemon-Name-Data')
).pop().innerHTML;
});
expect(output).toMatchSnapshot();
} finally {
browser.close();
}
});
it('correctly bundles files in production', async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(`http://localhost:${global.appProdPort}/`);
await page.waitForSelector('.Pokemon-Name-Data');
const output = await page.evaluate(() => {
return Array.from(
document.getElementsByClassName('Pokemon-Name-Data')
).pop().innerHTML;
});
expect(output).toMatchSnapshot();
} finally {
browser.close();
}
});
});

View File

@@ -1,7 +0,0 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/*.test.js'],
testPathIgnorePatterns: ['/src/', 'node_modules'],
setupTestFrameworkScriptFile: './setupBrowserTests.js',
forceExit: true,
};

View File

@@ -1,9 +0,0 @@
const fs = require('fs-extra');
const tempy = require('tempy');
beforeEach(() => {
global.testDirectory = tempy.directory();
jest.setTimeout(1000 * 60 * 5);
});
afterEach(() => {
fs.removeSync(global.testDirectory);
});

View File

@@ -1,5 +0,0 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/*.test.js'],
setupTestFrameworkScriptFile: './setupOutputTests.js',
};

View File

@@ -1,6 +0,0 @@
beforeAll(() => {
jest.setTimeout(1000 * 60 * 5);
});
beforeEach(() => {
jest.setTimeout(1000 * 60 * 5);
});

View File

@@ -1,157 +0,0 @@
const {
bootstrap,
getOutputDevelopment,
getOutputProduction,
} = require('../../utils');
const fs = require('fs-extra');
const path = require('path');
const Semaphore = require('async-sema');
const tempy = require('tempy');
describe('webpack message formatting', () => {
const semaphore = new Semaphore(1, { capacity: Infinity });
let testDirectory;
beforeAll(async () => {
testDirectory = tempy.directory();
await bootstrap({ directory: testDirectory, template: __dirname });
});
beforeEach(async () => {
await semaphore.acquire();
});
afterEach(async () => {
fs.removeSync(path.join(testDirectory, 'src', 'App.js'));
semaphore.release();
});
it('formats babel syntax error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppBabel.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats css syntax error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppCss.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats unknown export', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppUnknownExport.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats aliased unknown export', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppAliasUnknownExport.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats no default export', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppNoDefault.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats missing package', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppMissingPackage.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats eslint warning', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppLintWarning.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
const sizeIndex = response.stdout.indexOf('File sizes after gzip');
if (sizeIndex !== -1) {
response.stdout = response.stdout.substring(0, sizeIndex);
}
expect(response).toMatchSnapshot();
});
it('formats eslint error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppLintError.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('helps when users tries to use sass', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppSass.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats file not found error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppUnknownFile.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
it('formats case sensitive path error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppIncorrectCase.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputDevelopment({ directory: testDirectory });
if (process.platform === 'darwin') {
expect(response.stderr).toMatch(
`Cannot find file: 'export5.js' does not match the corresponding name on disk: './src/Export5.js'.`
);
} else {
expect(response.stderr).not.toEqual(''); // TODO: figure out how we can test this on Linux/Windows
// I believe getting this working requires we tap into enhanced-resolve
// pipeline, which is debt we don't want to take on right now.
}
});
it('formats out of scope error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppOutOfScopeImport.js'),
path.join(testDirectory, 'src', 'App.js')
);
const response = await getOutputProduction({ directory: testDirectory });
expect(response).toMatchSnapshot();
});
});

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -1,17 +0,0 @@
const {
bootstrap,
isSuccessfulDevelopment,
isSuccessfulProduction,
} = require('../../utils');
beforeEach(async () => {
await bootstrap({ directory: global.testDirectory, template: __dirname });
});
describe('bootstrap sass', () => {
it('builds in development', async () => {
await isSuccessfulDevelopment({ directory: global.testDirectory });
});
it('builds in production', async () => {
await isSuccessfulProduction({ directory: global.testDirectory });
});
});

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -1,17 +0,0 @@
const {
bootstrap,
isSuccessfulDevelopment,
isSuccessfulProduction,
} = require('../../utils');
beforeEach(async () => {
await bootstrap({ directory: global.testDirectory, template: __dirname });
});
describe('builds-with-multiple-runtimes', () => {
it('builds in development', async () => {
await isSuccessfulDevelopment({ directory: global.testDirectory });
});
it('builds in production', async () => {
await isSuccessfulProduction({ directory: global.testDirectory });
});
});

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -1,13 +0,0 @@
const { bootstrap, isSuccessfulTest } = require('../../utils');
beforeEach(async () => {
await bootstrap({ directory: global.testDirectory, template: __dirname });
});
describe('issue #5176 (flow class properties interaction)', () => {
it('passes tests', async () => {
await isSuccessfulTest({
directory: global.testDirectory,
jestEnvironment: 'node',
});
});
});

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -1,37 +0,0 @@
const fs = require('fs-extra');
const globby = require('globby');
const path = require('path');
const {
bootstrap,
isSuccessfulDevelopment,
isSuccessfulProduction,
} = require('../../utils');
beforeEach(async () => {
await bootstrap({ directory: global.testDirectory, template: __dirname });
});
describe('relative paths', () => {
// TODO: enable when development relative paths are supported
xit('builds in development', async () => {
await isSuccessfulDevelopment({ directory: global.testDirectory });
});
it('builds in production', async () => {
await isSuccessfulProduction({ directory: global.testDirectory });
const buildDir = path.join(global.testDirectory, 'build');
const cssFile = path.join(
buildDir,
globby.sync('**/*.css', { cwd: buildDir }).pop()
);
const svgFile = path.join(
buildDir,
globby.sync('**/*.svg', { cwd: buildDir }).pop()
);
const desiredPath = /url\((.+?)\)/
.exec(fs.readFileSync(cssFile, 'utf8'))
.pop();
expect(path.resolve(path.join(path.dirname(cssFile), desiredPath))).toBe(
path.resolve(svgFile)
);
});
});

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -1,10 +0,0 @@
const fs = require('fs-extra');
const tempy = require('tempy');
beforeEach(() => {
global.testDirectory = tempy.directory();
jest.setTimeout(1000 * 60 * 5);
});
afterEach(() => {
fs.removeSync(global.testDirectory);
});

View File

@@ -1,177 +0,0 @@
const execa = require('execa');
const fs = require('fs-extra');
const getPort = require('get-port');
const path = require('path');
const os = require('os');
const stripAnsi = require('strip-ansi');
async function bootstrap({ directory, template }) {
const shouldInstallScripts = process.env.CI && process.env.CI !== 'false';
await Promise.all(
['public/', 'src/', 'package.json'].map(async file =>
fs.copy(path.join(template, file), path.join(directory, file))
)
);
if (shouldInstallScripts) {
const packageJson = fs.readJsonSync(path.join(directory, 'package.json'));
packageJson.dependencies = Object.assign({}, packageJson.dependencies, {
'react-scripts': 'latest',
});
fs.writeJsonSync(path.join(directory, 'package.json'), packageJson);
}
await execa('yarnpkg', ['install', '--mutex', 'network'], { cwd: directory });
if (!shouldInstallScripts) {
fs.ensureSymlinkSync(
path.resolve(
path.join(
__dirname,
'..',
'packages',
'react-scripts',
'bin',
'react-scripts.js'
)
),
path.join(directory, 'node_modules', '.bin', 'react-scripts')
);
await execa('yarnpkg', ['link', 'react-scripts'], { cwd: directory });
}
}
async function isSuccessfulDevelopment({ directory }) {
const { stdout, stderr } = await execa(
'./node_modules/.bin/react-scripts',
['start', '--smoke-test'],
{
cwd: directory,
env: { BROWSER: 'none', PORT: await getPort() },
}
);
if (!/Compiled successfully/.test(stdout)) {
throw new Error(`stdout: ${stdout}${os.EOL + os.EOL}stderr: ${stderr}`);
}
}
async function isSuccessfulProduction({ directory }) {
const { stdout, stderr } = await execa(
'./node_modules/.bin/react-scripts',
['build'],
{
cwd: directory,
}
);
if (!/Compiled successfully/.test(stdout)) {
throw new Error(`stdout: ${stdout}${os.EOL + os.EOL}stderr: ${stderr}`);
}
}
async function isSuccessfulTest({ directory, jestEnvironment = 'jsdom' }) {
await execa(
'./node_modules/.bin/react-scripts',
['test', '--env', jestEnvironment, '--ci'],
{
cwd: directory,
env: { CI: 'true' },
}
);
}
async function getOutputDevelopment({ directory, env = {} }) {
try {
const { stdout, stderr } = await execa(
'./node_modules/.bin/react-scripts',
['start', '--smoke-test'],
{
cwd: directory,
env: Object.assign(
{},
{
BROWSER: 'none',
PORT: await getPort(),
CI: 'false',
FORCE_COLOR: '0',
},
env
),
}
);
return { stdout: stripAnsi(stdout), stderr: stripAnsi(stderr) };
} catch (err) {
return {
stdout: '',
stderr: stripAnsi(
err.message
.split(os.EOL)
.slice(2)
.join(os.EOL)
),
};
}
}
async function getOutputProduction({ directory, env = {} }) {
try {
const { stdout, stderr } = await execa(
'./node_modules/.bin/react-scripts',
['build'],
{
cwd: directory,
env: Object.assign({}, { CI: 'false', FORCE_COLOR: '0' }, env),
}
);
return { stdout: stripAnsi(stdout), stderr: stripAnsi(stderr) };
} catch (err) {
return {
stdout: '',
stderr: stripAnsi(
err.message
.split(os.EOL)
.slice(2)
.join(os.EOL)
),
};
}
}
async function startDevelopmentServer({ directory, env = {} }) {
const port = await getPort();
execa('./node_modules/.bin/react-scripts', ['start'], {
cwd: directory,
env: Object.assign(
{},
{
BROWSER: 'none',
PORT: port,
CI: 'false',
FORCE_COLOR: '0',
},
env
),
});
return port;
}
async function startProductionServer({ directory, env = {} }) {
const port = await getPort();
await execa('./node_modules/.bin/react-scripts', ['build'], {
cwd: directory,
env: Object.assign({}, { CI: 'false' }, env),
});
execa('./node_modules/.bin/serve', ['-s', 'build', '-p', port], {
cwd: directory,
});
return port;
}
module.exports = {
bootstrap,
isSuccessfulDevelopment,
isSuccessfulProduction,
isSuccessfulTest,
getOutputDevelopment,
getOutputProduction,
startDevelopmentServer,
startProductionServer,
};

View File

@@ -19,7 +19,6 @@
"compile:lockfile": "node tasks/compile-lockfile.js"
},
"devDependencies": {
"async-sema": "^2.1.3",
"eslint": "5.6.0",
"execa": "1.0.0",
"fs-extra": "^7.0.0",
@@ -36,7 +35,8 @@
"puppeteer": "^1.8.0",
"strip-ansi": "^4.0.0",
"svg-term-cli": "^2.1.1",
"tempy": "^0.2.1"
"tempy": "^0.2.1",
"wait-for-localhost": "2.0.1"
},
"husky": {
"hooks": {

View File

@@ -95,14 +95,9 @@ git clean -df
# Now that we have published them, run all tests as if they were released.
# ******************************************************************************
# Browser tests
CI=true ./node_modules/.bin/jest --config fixtures/browser/jest.config.js
# Smoke tests
CI=true ./node_modules/.bin/jest --config fixtures/smoke/jest.config.js
# Output tests
CI=true ./node_modules/.bin/jest --config fixtures/output/jest.config.js
# Run all tests
cd test/
CI=true ../node_modules/.bin/jest -w 2
# Cleanup
cleanup

45
test/README.md Normal file
View File

@@ -0,0 +1,45 @@
# Create React App End-to-End Tests
## Usage
These tests ensure various functionality contracts are upheld across dependency upgrades.
To get started locally, run `cd packages/react-scripts/ && yarn link; cd ../../test/ && ../node_modules/.bin/jest --watchAll`.
It's suggested that you filter down tests to avoid re-running everything. The most common tests will be the webpack messages.<br>
To only run the webpack messages, type `p` followed by `webpack-message` and press `[enter]`.
## How do these work?
### `fixtures/`
Each `fixture/` gets spun up in a temporary directory and has its dependencies installed with Yarn PnP (for speed).<br>
To opt-out of PnP, create a `.disable-pnp` file in the specific fixture directory.
A global (`testSetup`) is created which has a few interesting properties:
- `testSetup.testDirectory`: the directory containing the test application
- `testSetup.scripts`: an object allowing you to invoke `react-scripts` commands and friends
All tests for each `fixture/` are then ran.
#### `testSetup.scripts`
##### `start`
This will run the `start` command, it can be ran asynchronously or blocking if `{ smoke: true }` is used.<br>
If ran asynchronously, it will return the `port` and a `done` function to clean up the process.
If ran blocking, it will return the `stdout` and `stderr` of the process.
##### `build`
This will run the `build` command and return the `stdout` and `stderr` of the process.
##### `test`
This will run the `test` command and return the `stdout` and `stderr` of the process.
##### `serve`
This will run serve the application.
It will return the `port` and a `done` function to clean up the process.

21
test/fixtures/__shared__/test-setup.js vendored Normal file
View File

@@ -0,0 +1,21 @@
const path = require('path');
const fs = require('fs-extra');
const TestSetup = require('./util/setup');
const fixturePath = path.dirname(module.parent.filename);
const fixtureName = path.basename(fixturePath);
const disablePnp = fs.existsSync(path.resolve(fixturePath, '.disable-pnp'));
const testSetup = new TestSetup(fixtureName, fixturePath, {
pnp: !disablePnp,
});
beforeAll(async () => {
await testSetup.setup();
}, 1000 * 60 * 5);
afterAll(async () => {
await testSetup.teardown();
});
beforeEach(() => jest.setTimeout(1000 * 60 * 5));
module.exports = testSetup;

113
test/fixtures/__shared__/util/scripts.js vendored Normal file
View File

@@ -0,0 +1,113 @@
const execa = require('execa');
const getPort = require('get-port');
const os = require('os');
const stripAnsi = require('strip-ansi');
const waitForLocalhost = require('wait-for-localhost');
function stripYarn(output) {
let lines = output.split('\n');
let runIndex = lines.findIndex(line => line.match(/^yarn run/));
if (runIndex !== -1) {
lines.splice(0, runIndex + 2);
lines = lines.filter(line => !line.match(/^info Visit.*yarnpkg/));
}
return lines.join('\n');
}
function execaSafe(...args) {
return execa(...args)
.then(({ stdout, stderr, ...rest }) => ({
fulfilled: true,
rejected: false,
stdout: stripYarn(stripAnsi(stdout)),
stderr: stripYarn(stripAnsi(stderr)),
...rest,
}))
.catch(err => ({
fulfilled: false,
rejected: true,
reason: err,
stdout: '',
stderr: stripYarn(
stripAnsi(
err.message
.split(os.EOL)
.slice(2)
.join(os.EOL)
)
),
}));
}
module.exports = class ReactScripts {
constructor(root) {
this.root = root;
}
async start({ smoke = false, env = {} } = {}) {
const port = await getPort();
const options = {
cwd: this.root,
env: Object.assign(
{},
{
CI: 'false',
FORCE_COLOR: '0',
BROWSER: 'none',
PORT: port,
},
env
),
};
if (smoke) {
return await execaSafe('yarnpkg', ['start', '--smoke-test'], options);
}
const startProcess = execa('yarnpkg', ['start'], options);
await waitForLocalhost(port);
return {
port,
done() {
startProcess.kill('SIGKILL');
},
};
}
async build({ env = {} } = {}) {
return await execaSafe('yarnpkg', ['build'], {
cwd: this.root,
env: Object.assign({}, { CI: 'false', FORCE_COLOR: '0' }, env),
});
}
async serve() {
const port = await getPort();
const serveProcess = execa(
'yarnpkg',
['serve', '--', '-p', port, '-s', 'build/'],
{
cwd: this.root,
}
);
await waitForLocalhost(port);
return {
port,
done() {
serveProcess.kill('SIGKILL');
},
};
}
async test({ jestEnvironment = 'jsdom', env = {} } = {}) {
return await execaSafe(
'yarnpkg',
['test', '--env', jestEnvironment, '--ci'],
{
cwd: this.root,
env: Object.assign({}, { CI: 'true' }, env),
}
);
}
};

104
test/fixtures/__shared__/util/setup.js vendored Normal file
View File

@@ -0,0 +1,104 @@
const execa = require('execa');
const fs = require('fs-extra');
const path = require('path');
const tempy = require('tempy');
const ReactScripts = require('./scripts');
module.exports = class TestSetup {
constructor(fixtureName, templateDirectory, { pnp = true } = {}) {
this.fixtureName = fixtureName;
this.templateDirectory = templateDirectory;
this.testDirectory = null;
this._scripts = null;
this.setup = this.setup.bind(this);
this.teardown = this.teardown.bind(this);
this.isLocal = !(process.env.CI && process.env.CI !== 'false');
this.settings = { pnp: pnp && !this.isLocal };
}
async setup() {
await this.teardown();
this.testDirectory = tempy.directory();
await fs.copy(
path.resolve(__dirname, '..', 'template'),
this.testDirectory
);
await fs.copy(this.templateDirectory, this.testDirectory);
await fs.remove(path.resolve(this.testDirectory, 'test.partial.js'));
await fs.remove(path.resolve(this.testDirectory, '.disable-pnp'));
const packageJson = await fs.readJson(
path.resolve(this.testDirectory, 'package.json')
);
const shouldInstallScripts = !this.isLocal;
if (shouldInstallScripts) {
packageJson.dependencies = Object.assign({}, packageJson.dependencies, {
'react-scripts': 'latest',
});
}
packageJson.scripts = Object.assign({}, packageJson.scripts, {
start: 'react-scripts start',
build: 'react-scripts build',
test: 'react-scripts test',
});
packageJson.license = packageJson.license || 'UNLICENSED';
await fs.writeJson(
path.resolve(this.testDirectory, 'package.json'),
packageJson
);
await execa(
'yarnpkg',
[
'install',
this.settings.pnp ? '--enable-pnp' : null,
'--mutex',
'network',
].filter(Boolean),
{
cwd: this.testDirectory,
}
);
if (!shouldInstallScripts) {
await fs.ensureSymlink(
path.resolve(
path.resolve(
__dirname,
'../../../..',
'packages',
'react-scripts',
'bin',
'react-scripts.js'
)
),
path.join(this.testDirectory, 'node_modules', '.bin', 'react-scripts')
);
await execa('yarnpkg', ['link', 'react-scripts'], {
cwd: this.testDirectory,
});
}
}
get scripts() {
if (this.testDirectory == null) {
return null;
}
if (this._scripts == null) {
this._scripts = new ReactScripts(this.testDirectory);
}
return this._scripts;
}
async teardown() {
if (this.testDirectory != null) {
await fs.remove(this.testDirectory);
this.testDirectory = null;
this._scripts = null;
}
}
};

View File

View File

@@ -0,0 +1,16 @@
const testSetup = require('../__shared__/test-setup');
if (testSetup.isLocal) {
// TODO: make this work locally
test('skipped locally', () => {});
} else {
test('builds in development', async () => {
const { fulfilled } = await testSetup.scripts.start({ smoke: true });
expect(fulfilled).toBe(true);
});
test('builds in production', async () => {
const { fulfilled } = await testSetup.scripts.build();
expect(fulfilled).toBe(true);
});
}

View File

@@ -0,0 +1,10 @@
const testSetup = require('../__shared__/test-setup');
test('builds in development', async () => {
const { fulfilled } = await testSetup.scripts.start({ smoke: true });
expect(fulfilled).toBe(true);
});
test('builds in production', async () => {
const { fulfilled } = await testSetup.scripts.build();
expect(fulfilled).toBe(true);
});

View File

@@ -1,6 +1,7 @@
{
"dependencies": {
"dva": "2.4.0",
"history": "4.7.2",
"ky": "0.3.0",
"react": "latest",
"react-dom": "latest"

View File

@@ -0,0 +1,8 @@
const testSetup = require('../__shared__/test-setup');
test('passes tests', async () => {
const { fulfilled } = await testSetup.scripts.test({
jestEnvironment: 'node',
});
expect(fulfilled).toBe(true);
});

View File

@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`can use mjs library in development 1`] = `"Pikachu"`;
exports[`can use mjs library in production 1`] = `"Pikachu"`;

43
test/fixtures/mjs-support/index.test.js vendored Normal file
View File

@@ -0,0 +1,43 @@
const testSetup = require('../__shared__/test-setup');
const puppeteer = require('puppeteer');
test('can use mjs library in development', async () => {
const { port, done } = await testSetup.scripts.start();
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(`http://localhost:${port}/`);
await page.waitForSelector('.Pokemon-Name-Data', { timeout: 0 });
const output = await page.evaluate(() => {
return Array.from(
document.getElementsByClassName('Pokemon-Name-Data')
).pop().innerHTML;
});
expect(output).toMatchSnapshot();
} finally {
browser.close();
done();
}
});
test('can use mjs library in production', async () => {
await testSetup.scripts.build();
const { port, done } = await testSetup.scripts.serve();
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(`http://localhost:${port}/`);
await page.waitForSelector('.Pokemon-Name-Data', { timeout: 0 });
const output = await page.evaluate(() => {
return Array.from(
document.getElementsByClassName('Pokemon-Name-Data')
).pop().innerHTML;
});
expect(output).toMatchSnapshot();
} finally {
browser.close();
done();
}
});

View File

@@ -3,6 +3,7 @@
"apollo-boost": "0.1.16",
"graphql": "14.0.2",
"react-apollo": "2.2.1",
"apollo-client": "2.4.2",
"react": "latest",
"react-dom": "latest",
"serve": "10.0.2"

View File

@@ -0,0 +1,25 @@
const testSetup = require('../__shared__/test-setup');
const fs = require('fs-extra');
const globby = require('globby');
const path = require('path');
test('contains a relative path in production build', async () => {
await testSetup.scripts.build();
const buildDir = path.join(testSetup.testDirectory, 'build');
const cssFile = path.join(
buildDir,
globby.sync('**/*.css', { cwd: buildDir }).pop()
);
const svgFile = path.join(
buildDir,
globby.sync('**/*.svg', { cwd: buildDir }).pop()
);
const desiredPath = /url\((.+?)\)/
.exec(fs.readFileSync(cssFile, 'utf8'))
.pop();
expect(path.resolve(path.join(path.dirname(cssFile), desiredPath))).toBe(
path.resolve(svgFile)
);
});

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`webpack message formatting formats aliased unknown export 1`] = `
exports[`formats aliased unknown export 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -14,7 +14,7 @@ Attempted import error: 'bar' is not exported from './AppUnknownExport' (importe
}
`;
exports[`webpack message formatting formats babel syntax error 1`] = `
exports[`formats babel syntax error 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -36,7 +36,7 @@ Syntax error: Unterminated JSX contents (8:13)
}
`;
exports[`webpack message formatting formats css syntax error 1`] = `
exports[`formats css syntax error 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -56,7 +56,7 @@ Syntax error: Unexpected } (3:2)
}
`;
exports[`webpack message formatting formats eslint error 1`] = `
exports[`formats eslint error 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -72,7 +72,7 @@ Search for the keywords to learn more about each error.
}
`;
exports[`webpack message formatting formats eslint warning 1`] = `
exports[`formats eslint warning 1`] = `
Object {
"stderr": "",
"stdout": "Creating an optimized production build...
@@ -88,7 +88,7 @@ To ignore, add // eslint-disable-next-line to the line before.
}
`;
exports[`webpack message formatting formats file not found error 1`] = `
exports[`formats file not found error 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -102,7 +102,7 @@ Cannot find file './ThisFileSouldNotExist' in './src'.
}
`;
exports[`webpack message formatting formats missing package 1`] = `
exports[`formats missing package 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -118,7 +118,7 @@ You can install this package by running: yarn add unknown-package.
}
`;
exports[`webpack message formatting formats no default export 1`] = `
exports[`formats no default export 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -132,7 +132,7 @@ Attempted import error: './ExportNoDefault' does not contain a default export (i
}
`;
exports[`webpack message formatting formats out of scope error 1`] = `
exports[`formats out of scope error 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -147,7 +147,7 @@ You can either move it inside src/, or add a symlink to it from project's node_m
}
`;
exports[`webpack message formatting formats unknown export 1`] = `
exports[`formats unknown export 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.
@@ -161,7 +161,7 @@ Attempted import error: 'bar' is not exported from './AppUnknownExport'.
}
`;
exports[`webpack message formatting helps when users tries to use sass 1`] = `
exports[`helps when users tries to use sass 1`] = `
Object {
"stderr": "Creating an optimized production build...
Failed to compile.

View File

@@ -0,0 +1,136 @@
const testSetup = require('../__shared__/test-setup');
const fs = require('fs-extra');
const path = require('path');
test('formats babel syntax error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppBabel.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats css syntax error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppCss.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats unknown export', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppUnknownExport.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats aliased unknown export', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppAliasUnknownExport.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats no default export', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppNoDefault.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats missing package', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppMissingPackage.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats eslint warning', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppLintWarning.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
let { stdout, stderr } = await testSetup.scripts.build();
const sizeIndex = stdout.indexOf('File sizes after gzip');
if (sizeIndex !== -1) {
stdout = stdout.substring(0, sizeIndex);
}
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats eslint error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppLintError.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('helps when users tries to use sass', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppSass.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats file not found error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppUnknownFile.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});
test('formats case sensitive path error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppIncorrectCase.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.start({ smoke: true });
if (process.platform === 'darwin') {
expect(stderr).toMatch(
`Cannot find file: 'export5.js' does not match the corresponding name on disk: './src/Export5.js'.`
);
} else {
expect(stderr).not.toEqual(''); // TODO: figure out how we can test this on Linux/Windows
// I believe getting this working requires we tap into enhanced-resolve
// pipeline, which is debt we don't want to take on right now.
}
});
test('formats out of scope error', async () => {
fs.copySync(
path.join(__dirname, 'src', 'AppOutOfScopeImport.js'),
path.join(testSetup.testDirectory, 'src', 'App.js')
);
const { stdout, stderr } = await testSetup.scripts.build();
expect({ stdout, stderr }).toMatchSnapshot();
});

View File

@@ -1,6 +1,7 @@
'use strict';
module.exports = {
testEnvironment: 'node',
testMatch: ['**/*.test.js'],
testMatch: ['<rootDir>/**/*.test.js'],
testPathIgnorePatterns: ['/src/', 'node_modules'],
setupTestFrameworkScriptFile: './setupSmokeTests.js',
};