'use strict';
|
|
const debug = require('debug')('egg-bin');
|
const fs = require('fs');
|
const path = require('path');
|
const globby = require('globby');
|
const Command = require('../command');
|
const changed = require('jest-changed-files');
|
|
class TestCommand extends Command {
|
constructor(rawArgv) {
|
super(rawArgv);
|
this.usage = 'Usage: egg-bin test [files] [options]';
|
this.options = {
|
require: {
|
description: 'require the given module',
|
alias: 'r',
|
type: 'array',
|
},
|
grep: {
|
description: 'only run tests matching <pattern>',
|
alias: 'g',
|
type: 'array',
|
},
|
timeout: {
|
description: 'set test-case timeout in milliseconds',
|
alias: 't',
|
type: 'number',
|
},
|
'full-trace': {
|
description: 'display the full stack trace',
|
},
|
changed: {
|
description: 'only test with changed files and match ${cwd}/test/**/*.test.(js|ts)',
|
alias: 'c',
|
},
|
};
|
}
|
|
get description() {
|
return 'Run test with mocha';
|
}
|
|
* run(context) {
|
const opt = {
|
env: Object.assign({
|
NODE_ENV: 'test',
|
}, context.env),
|
execArgv: context.execArgv,
|
};
|
const mochaFile = require.resolve('mocha/bin/_mocha');
|
const testArgs = yield this.formatTestArgs(context);
|
if (!testArgs) return;
|
debug('run test: %s %s', mochaFile, testArgs.join(' '));
|
yield this.helper.forkNode(mochaFile, testArgs, opt);
|
}
|
|
get context() {
|
const context = super.context;
|
const { argv, execArgvObj } = context;
|
|
// remove ts-node, ts-node and espower-typescript can't coexist
|
// because espower-typescript@9 has already register ts-node
|
if (argv.typescript) {
|
execArgvObj.require.splice(execArgvObj.require.indexOf(require.resolve('ts-node/register')), 1);
|
}
|
|
return context;
|
}
|
|
/**
|
* format test args then change it to array style
|
* @param {Object} context - { cwd, argv, ...}
|
* @return {Array} [ '--require=xxx', 'xx.test.js' ]
|
* @protected
|
*/
|
* formatTestArgs({ argv, debugOptions }) {
|
const testArgv = Object.assign({}, argv);
|
|
/* istanbul ignore next */
|
testArgv.timeout = testArgv.timeout || process.env.TEST_TIMEOUT || 60000;
|
testArgv.reporter = testArgv.reporter || process.env.TEST_REPORTER;
|
// force exit
|
testArgv.exit = true;
|
|
// whether is debug mode, if pass --inspect then `debugOptions` is valid
|
// others like WebStorm 2019 will pass NODE_OPTIONS, and egg-bin itself will be debug, so could detect `process.env.JB_DEBUG_FILE`.
|
|
if (debugOptions || process.env.JB_DEBUG_FILE) {
|
// --no-timeout
|
testArgv.timeout = false;
|
}
|
|
// collect require
|
let requireArr = testArgv.require || testArgv.r || [];
|
/* istanbul ignore next */
|
if (!Array.isArray(requireArr)) requireArr = [ requireArr ];
|
|
// clean mocha stack, inspired by https://github.com/rstacruz/mocha-clean
|
// [mocha built-in](https://github.com/mochajs/mocha/blob/master/lib/utils.js#L738) don't work with `[npminstall](https://github.com/cnpm/npminstall)`, so we will override it.
|
if (!testArgv.fullTrace) requireArr.unshift(require.resolve('../mocha-clean'));
|
|
requireArr.push(require.resolve('co-mocha'));
|
|
if (requireArr.includes('intelli-espower-loader')) {
|
console.warn('[egg-bin] don\'t need to manually require `intelli-espower-loader` anymore');
|
} else {
|
requireArr.push(require.resolve('intelli-espower-loader'));
|
}
|
|
// for power-assert
|
if (testArgv.typescript) {
|
// remove ts-node in context getter on top.
|
requireArr.push(require.resolve('espower-typescript/guess'));
|
}
|
|
testArgv.require = requireArr;
|
|
let pattern;
|
// changed
|
if (testArgv.changed) {
|
pattern = yield this._getChangedTestFiles();
|
if (!pattern.length) {
|
console.log('No changed test files');
|
return;
|
}
|
}
|
|
if (!pattern) {
|
// specific test files
|
pattern = testArgv._.slice();
|
}
|
if (!pattern.length && process.env.TESTS) {
|
pattern = process.env.TESTS.split(',');
|
}
|
|
// collect test files
|
if (!pattern.length) {
|
pattern = [ `test/**/*.test.${testArgv.typescript ? 'ts' : 'js'}` ];
|
}
|
pattern = pattern.concat([ '!test/fixtures', '!test/node_modules' ]);
|
|
// expand glob and skip node_modules and fixtures
|
const files = globby.sync(pattern);
|
files.sort();
|
|
if (files.length === 0) {
|
console.log(`No test files found with ${pattern}`);
|
return;
|
}
|
|
// auto add setup file as the first test file
|
const setupFile = path.join(process.cwd(), 'test/.setup.js');
|
if (fs.existsSync(setupFile)) {
|
files.unshift(setupFile);
|
}
|
testArgv._ = files;
|
|
// remove alias
|
testArgv.$0 = undefined;
|
testArgv.r = undefined;
|
testArgv.t = undefined;
|
testArgv.g = undefined;
|
testArgv.typescript = undefined;
|
|
return this.helper.unparseArgv(testArgv);
|
}
|
|
* _getChangedTestFiles() {
|
const cwd = process.cwd();
|
const res = yield changed.getChangedFilesForRoots([ cwd ]);
|
const changedFiles = res.changedFiles;
|
const files = [];
|
for (const file of changedFiles) {
|
// only find ${cwd}/test/**/*.test.(js|ts)
|
if (file.startsWith(path.join(cwd, 'test')) && file.match(/\.test\.(js|ts)$/)) {
|
files.push(file);
|
}
|
}
|
return files;
|
}
|
}
|
|
module.exports = TestCommand;
|