diff options
-rw-r--r-- | config/karma.config.js | 26 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rwxr-xr-x | scripts/frontend/test.js | 114 |
3 files changed, 136 insertions, 6 deletions
diff --git a/config/karma.config.js b/config/karma.config.js index b2fc3a32816..2a5bf3581e0 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -9,11 +9,22 @@ const IS_EE = require('./helpers/is_ee_env'); const ROOT_PATH = path.resolve(__dirname, '..'); const SPECS_PATH = /^(?:\.[\\\/])?(ee[\\\/])?spec[\\\/]javascripts[\\\/]/; -function fatalError(message) { +function exitError(message) { console.error(chalk.red(`\nError: ${message}\n`)); process.exit(1); } +function exitWarn(message) { + console.error(chalk.yellow(`\nWarn: ${message}\n`)); + process.exit(0); +} + +function exit(message, isError = true) { + const fn = isError ? exitError : exitWarn; + + fn(message); +} + // disable problematic options webpackConfig.entry = undefined; webpackConfig.mode = 'development'; @@ -31,7 +42,8 @@ webpackConfig.plugins.push( }), ); -const specFilters = argumentsParser +const options = argumentsParser + .option('--no-fail-on-empty-test-suite') .option( '-f, --filter-spec [filter]', 'Filter run spec files by path. Multiple filters are like a logical OR.', @@ -41,7 +53,9 @@ const specFilters = argumentsParser }, [], ) - .parse(process.argv).filterSpec; + .parse(process.argv); + +const specFilters = options.filterSpec; const createContext = (specFiles, regex, suffix) => { const newContext = specFiles.reduce((context, file) => { @@ -73,11 +87,13 @@ if (specFilters.length) { filteredSpecFiles = [...new Set(filteredSpecFiles)]; if (filteredSpecFiles.length < 1) { - fatalError('Your filter did not match any test files.'); + const isError = options.failOnEmptyTestSuite; + + exit('Your filter did not match any test files.', isError); } if (!filteredSpecFiles.every(file => SPECS_PATH.test(file))) { - fatalError('Test files must be located within /spec/javascripts.'); + exitError('Test files must be located within /spec/javascripts.'); } const CE_FILES = filteredSpecFiles.filter(file => !file.startsWith('ee')); diff --git a/package.json b/package.json index 099d0b3d8fa..381accff23b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* ee/app/assets/stylesheets/**/*.* !**/vendors/** --custom-formatter node_modules/stylelint-error-string-formatter", "stylelint-file": "node node_modules/stylelint/bin/stylelint.js", "stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js", - "test": "yarn jest && yarn karma", + "test": "node scripts/frontend/test", "webpack": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.config.js", "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js" }, diff --git a/scripts/frontend/test.js b/scripts/frontend/test.js new file mode 100755 index 00000000000..dab7176f8c1 --- /dev/null +++ b/scripts/frontend/test.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node + +const { spawn } = require('child_process'); +const { EOL } = require('os'); +const program = require('commander'); +const chalk = require('chalk'); + +const JEST_ROUTE = 'spec/frontend'; +const KARMA_ROUTE = 'spec/javascripts'; +const COMMON_ARGS = ['--colors']; +const JEST_ARGS = ['--passWithNoTests']; +const KARMA_ARGS = ['--no-fail-on-empty-test-suite']; +const SUCCESS_CODE = 0; + +program + .version('0.1.0') + .usage('[options] <file ...>') + .option('-p, --parallel', 'Run tests suites in parallel') + .parse(process.argv); + +const isSuccess = code => code === SUCCESS_CODE; + +const combineExitCodes = codes => { + const firstFail = codes.find(x => !isSuccess(x)); + + return firstFail === undefined ? SUCCESS_CODE : firstFail; +}; + +const skipIfFail = fn => code => (isSuccess(code) ? fn() : code); + +const endWithEOL = str => (str[str.length - 1] === '\n' ? str : `${str}${EOL}`); + +const runTests = paths => { + if (program.parallel) { + return Promise.all([runJest(paths), runKarma(paths)]).then(combineExitCodes); + } else { + return runJest(paths).then(skipIfFail(() => runKarma(paths))); + } +}; + +const spawnYarnScript = (cmd, args) => { + return new Promise((resolve, reject) => { + const proc = spawn('yarn', ['run', cmd, ...args]); + const output = data => { + const text = data + .toString() + .split(/\r?\n/g) + .map((line, idx, { length }) => + idx === length - 1 && !line ? line : `${chalk.gray(cmd)}: ${line}`, + ) + .join(EOL); + + return endWithEOL(text); + }; + + proc.stdout.on('data', data => { + process.stdout.write(output(data)); + }); + + proc.stderr.on('data', data => { + process.stderr.write(output(data)); + }); + + proc.on('close', code => { + process.stdout.write(output(`exited with code ${code}`)); + + // We resolve even on a failure code because a `reject` would cause + // Promise.all to reject immediately (without waiting for other promises) + // to finish. + resolve(code); + }); + }); +}; + +const runJest = args => { + return spawnYarnScript('jest', [...JEST_ARGS, ...COMMON_ARGS, ...toJestArgs(args)]); +}; + +const runKarma = args => { + return spawnYarnScript('karma', [...KARMA_ARGS, ...COMMON_ARGS, ...toKarmaArgs(args)]); +}; + +const replacePath = to => path => + path + .replace(JEST_ROUTE, to) + .replace(KARMA_ROUTE, to) + .replace('app/assets/javascripts', to); + +const replacePathForJest = replacePath(JEST_ROUTE); + +const replacePathForKarma = replacePath(KARMA_ROUTE); + +const toJestArgs = paths => paths.map(replacePathForJest); + +const toKarmaArgs = paths => + paths.reduce((acc, path) => acc.concat('-f', replacePathForKarma(path)), []); + +const main = paths => { + runTests(paths).then(code => { + console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); + if (isSuccess(code)) { + console.log(chalk.bgGreen(chalk.black('All tests passed :)'))); + } else { + console.log(chalk.bgRed(chalk.white(`Some tests failed :(`))); + } + console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); + + if (!isSuccess(code)) { + process.exit(code); + } + }); +}; + +main(program.args); |