Skip to content

Commit

Permalink
refactor: Simplify test-runner logic (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmdartus committed Feb 3, 2021
1 parent 2c906c7 commit 082c407
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 234 deletions.
12 changes: 10 additions & 2 deletions bin/sfdx-lwc-jest
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ const { error } = require('../src/log');

const args = getArgs();
const runJest = require('../src/utils/test-runner');
runJest(args);

process.on('unhandledRejection', reason => {
runJest(args)
.catch((err) => {
error(err);
return 1;
})
.then((code) => {
process.exit(code);
});

process.on('unhandledRejection', (reason) => {
error(reason);
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"scripts": {
"check-license-headers": "node ./scripts/checkLicenseHeaders.js",
"lint": "eslint src/",
"lint": "eslint src/ tests/",
"format": "prettier --write '**/*.{js,json,md,html,css}'",
"format:check": "prettier --check '**/*.{js,json,md,html,css}'",
"release": "npm publish --access public",
Expand Down
9 changes: 1 addition & 8 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,4 @@ const jestConfig = {

const expectedApiVersion = '50.0';

const jestPath = (() => {
const packageJsonPath = require.resolve('jest/package.json');
const { bin } = require(packageJsonPath);

return path.resolve(path.dirname(packageJsonPath), bin);
})();

module.exports = { jestConfig, jestPath, expectedApiVersion };
module.exports = { jestConfig, expectedApiVersion };
3 changes: 1 addition & 2 deletions src/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ function info(message) {
console.log(`${chalk.blue('info')} ${message}`);
}

function error(message, code = 1) {
function error(message) {
console.error(`${chalk.red('error')} ${message}`);
process.exit(code);
}

module.exports = {
Expand Down
4 changes: 3 additions & 1 deletion src/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ const fs = require('fs');
const path = require('path');
const lwcResolver = require('@lwc/jest-resolver');

const { PROJECT_ROOT, getModulePaths, DEFAULT_NAMESPACE } = require('./utils/project.js');
const { PROJECT_ROOT, getModulePaths } = require('./utils/project.js');

const { getInfoFromId } = require('./utils/module.js');

const DEFAULT_NAMESPACE = 'c';

function isFile(file) {
let result;

Expand Down
13 changes: 0 additions & 13 deletions src/utils/jest.js

This file was deleted.

2 changes: 0 additions & 2 deletions src/utils/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const path = require('path');
const fg = require('fast-glob');

const PROJECT_ROOT = fs.realpathSync(process.cwd());
const DEFAULT_NAMESPACE = 'c';

let PATHS = [];

Expand Down Expand Up @@ -40,6 +39,5 @@ function getModulePaths() {
module.exports = {
PROJECT_ROOT,
getSfdxProjectJson,
DEFAULT_NAMESPACE,
getModulePaths,
};
31 changes: 0 additions & 31 deletions src/utils/shell.js

This file was deleted.

93 changes: 51 additions & 42 deletions src/utils/test-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,15 @@

const fs = require('fs');
const path = require('path');
const { jestRunner } = require('./jest');
const shell = require('./shell');

const { error, info } = require('../log');
const { spawn } = require('child_process');

const { PROJECT_ROOT, getSfdxProjectJson } = require('./project');

const { jestConfig, expectedApiVersion, jestPath } = require('../config');

// CLI options we do not want to pass along to Jest
// prettier-ignore
const OPTIONS_DISALLOW_LIST = [
'_',
'$0',
'debug', 'd',
'skipApiVersionCheck', 'skip-api-version-check'
];

function getOptions(argv) {
let options = [];
const { error, info } = require('../log');
const { jestConfig, expectedApiVersion } = require('../config');

Object.keys(argv).forEach((arg) => {
if (argv[arg] && !OPTIONS_DISALLOW_LIST.includes(arg)) {
options.push(`--${arg}`);
}
});
return options.concat(argv._);
}
// List of CLI options that should be passthrough to jest.
const JEST_PASSTHROUGH_OPTIONS = new Set(['coverage', 'updateSnapshot', 'verbose', 'watch']);

function validSourceApiVersion() {
const sfdxProjectJson = getSfdxProjectJson();
Expand All @@ -47,33 +28,61 @@ function validSourceApiVersion() {
}
}

function getJestPath() {
const packageJsonPath = require.resolve('jest/package.json');

const { bin } = require(packageJsonPath);
return path.resolve(path.dirname(packageJsonPath), bin);
}

function getJestArgs(argv) {
// Extract known options from parsed arguments
const knownOptions = Object.keys(argv)
.filter((key) => argv[key] && JEST_PASSTHROUGH_OPTIONS.has(key))
.map((key) => `--${key}`);

// Extract remaining options after `--`
const rest = argv._;

const jestArgs = [...knownOptions, ...rest];

// Force jest to run in band when debugging.
if (argv.debug) {
jestArgs.unshift('--runInBand');
}

// Provide default configuration when none is present at the project root.
const hasCustomConfig = fs.existsSync(path.resolve(PROJECT_ROOT, 'jest.config.js'));
if (!hasCustomConfig) {
jestArgs.unshift(`--config=${JSON.stringify(jestConfig)}`);
}

return jestArgs;
}

async function testRunner(argv) {
if (!argv.skipApiVersionCheck) {
validSourceApiVersion();
}

const hasCustomConfig = fs.existsSync(path.resolve(PROJECT_ROOT, 'jest.config.js'));
const config = hasCustomConfig ? [] : ['--config', JSON.stringify(jestConfig)];
const spawnCommand = 'node';
const spawnArgs = [getJestPath(), ...getJestArgs(argv)];

const options = getOptions(argv);
if (argv.debug) {
spawnArgs.unshift('--inspect-brk');

info('Running in debug mode...');
let commandArgs = ['--inspect-brk', jestPath, '--runInBand'];
commandArgs = commandArgs.concat(options);
if (!hasCustomConfig) {
commandArgs.push('--config=' + JSON.stringify(jestConfig));
}
const command = 'node';
info(command + ' ' + commandArgs.join(' '));

shell.runCommand(command, commandArgs);
} else {
// Jest will not set the env if not run from the bin executable
if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = 'test';
}
jestRunner.run([...config, ...options]);
info(`${spawnCommand} ${spawnArgs.join(' ')}`);
}

return new Promise((resolve) => {
const jest = spawn(spawnCommand, spawnArgs, {
env: process.env,
stdio: 'inherit',
});

jest.on('close', (code) => resolve(code));
});
}

module.exports = testRunner;
2 changes: 1 addition & 1 deletion src/utils/yargs.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
*/
'use strict';

const options = require('../options/options');
const yargs = require('yargs');

const { error, info } = require('../log');
const options = require('../options/options');

const argError = (msg, err, yargs) => {
if (err) {
Expand Down
21 changes: 5 additions & 16 deletions tests/help.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,11 @@
'use strict';

const { exec } = require('child_process');
const { promisify } = require('util');

const runInHelpMode = () => {
return new Promise((resolve, reject) => {
exec('node ./bin/sfdx-lwc-jest --help', (error, stdout) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
};
const promiseExec = promisify(exec);

test('--help attribute shows help', () => {
expect.assertions(1);
return runInHelpMode().then((stdout) => {
expect(stdout).toMatchSnapshot();
});
test('--help attribute shows help', async () => {
const { stdout } = await promiseExec('node ./bin/sfdx-lwc-jest --help');
expect(stdout).toMatchSnapshot();
});
97 changes: 56 additions & 41 deletions tests/test-runner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,71 @@
*/
'use strict';

const fakeJest = require('../src/utils/jest');
const fakeShell = require('../src/utils/shell');
const child_process = require('child_process');
const runJest = require('../src/utils/test-runner');

jest.mock('child_process');
jest.mock('../src/log');
jest.mock('../src/utils/project');
jest.mock('../src/utils/jest');
jest.mock('../src/utils/shell');

const runJest = require('../src/utils/test-runner');
beforeEach(() => {
child_process.spawn.mockReset();

const { jestPath } = require('../src/config');
child_process.spawn.mockImplementation(() => {
return {
on(name, cb) {
if (name === 'close') cb(0);
},
};
});
});

const generateArgs = ({ args = {}, advanced = [] } = {}) => {
args['_'] = advanced;
return args;
};
test('invokes node executable with jest path', async () => {
await runJest({ _: [] });
const [[cmd, args]] = child_process.spawn.mock.calls;

const defaultArgs = generateArgs();
expect(cmd).toContain('node');
expect(args[0]).toMatch(/bin\/jest\.js$/);
});

test('advanced params get passed to Jest', () => {
const advancedParam = '--maxWorkers=4';
const args = generateArgs({ advanced: [advancedParam] });
let jestParams = [];
fakeJest.jestRunner.run = jest.fn((array) => {
jestParams = array;
});
runJest(args);
expect(jestParams).toContain(advancedParam);
test('invokes jest with --runInBand and --inspect-brk in debug mode', async () => {
await runJest({ debug: true, _: [] });
const [[, args]] = child_process.spawn.mock.calls;

expect(args).toContain('--inspect-brk');
expect(args).toContain('--runInBand');
});

test('config is being passed', () => {
let jestParams = [];
fakeJest.jestRunner.run = jest.fn((array) => {
jestParams = array;
});
runJest(defaultArgs);
expect(jestParams.length).toBe(2);
expect(jestParams).toContain('--config');
test.each(['coverage', 'updateSnapshot', 'verbose', 'watch'])(
'invokes jest with option %s when present',
async (option) => {
await runJest({ [option]: true, _: [] });
const [[, args]] = child_process.spawn.mock.calls;

expect(args).toContain(`--${option}`);
},
);

test('invokes jest with position arguments when present', async () => {
await runJest({ _: ['--showConfig', 'my/test.js'] });
const [[, args]] = child_process.spawn.mock.calls;

expect(args).toContain(`--showConfig`);
expect(args).toContain(`my/test.js`);
});

test('debug flag runs node debugger', () => {
const debugAttr = '--inspect-brk';
const args = generateArgs({ args: { debug: true } });
const shallArgs = {};
fakeJest.jestRunner.run = jest.fn();
fakeShell.runCommand = jest.fn((command, args) => {
shallArgs.command = command;
shallArgs.args = args;
test('resolves with jest command exit code', async () => {
const EXIT_CODE = 42;
child_process.spawn.mockImplementation(() => {
return {
on(name, cb) {
if (name === 'close') {
cb(EXIT_CODE);
}
},
};
});
runJest(args);
expect(fakeJest.jestRunner.run).not.toHaveBeenCalled();
expect(shallArgs.args).toContain(debugAttr);
expect(shallArgs.args).toContain(jestPath);
expect(shallArgs.command).toBe('node');

const res = await runJest({ _: [] });
expect(res).toBe(EXIT_CODE);
});

0 comments on commit 082c407

Please sign in to comment.