Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate jest-cli to TypeScript #8024

Merged
merged 15 commits into from Mar 5, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -78,6 +78,7 @@
- `[jest-validate]`: Migrate to TypeScript ([#7991](https://github.com/facebook/jest/pull/7991))
- `[docs]`: Update CONTRIBUTING.md to add information about running jest with `jest-circus` locally ([#8013](https://github.com/facebook/jest/pull/8013)).
- `[@jest/core]`: Migrate to TypeScript ([#7998](https://github.com/facebook/jest/pull/7998))
- `[jest-cli]`: Migrate to TypeScript ([#8024](https://github.com/facebook/jest/pull/8024))

### Performance

Expand Down
2 changes: 2 additions & 0 deletions packages/jest-cli/package.json
Expand Up @@ -3,8 +3,10 @@
"description": "Delightful JavaScript Testing.",
"version": "24.1.0",
"main": "build/index.js",
"types": "build/index.d.ts",
"dependencies": {
"@jest/core": "^24.1.0",
"@jest/types": "^24.1.0",
"chalk": "^2.0.1",
"exit": "^0.1.2",
"import-local": "^2.0.0",
Expand Down
Expand Up @@ -6,55 +6,56 @@
*
*/

'use strict';

import type {Argv} from 'types/Argv';
import {Config} from '@jest/types';
import {check} from '../../cli/args';
import {buildArgv} from '../../cli';

describe('check', () => {
it('returns true if the arguments are valid', () => {
const argv: Argv = {};
const argv = {} as Config.Argv;
expect(check(argv)).toBe(true);
});

it('raises an exception if runInBand and maxWorkers are both specified', () => {
const argv: Argv = {maxWorkers: 2, runInBand: true};
const argv = {maxWorkers: 2, runInBand: true} as Config.Argv;
expect(() => check(argv)).toThrow(
'Both --runInBand and --maxWorkers were specified',
);
});

it('raises an exception if onlyChanged and watchAll are both specified', () => {
const argv: Argv = {onlyChanged: true, watchAll: true};
const argv = {onlyChanged: true, watchAll: true} as Config.Argv;
expect(() => check(argv)).toThrow(
'Both --onlyChanged and --watchAll were specified',
);
});

it('raises an exception when lastCommit and watchAll are both specified', () => {
const argv: Argv = {lastCommit: true, watchAll: true};
const argv = {lastCommit: true, watchAll: true} as Config.Argv;
expect(() => check(argv)).toThrow(
'Both --lastCommit and --watchAll were specified',
);
});

it('raises an exception if findRelatedTests is specified with no file paths', () => {
const argv: Argv = {_: [], findRelatedTests: true};
const argv = {
_: [] as Array<string>,
findRelatedTests: true,
} as Config.Argv;
expect(() => check(argv)).toThrow(
'The --findRelatedTests option requires file paths to be specified',
);
});

it('raises an exception if maxWorkers is specified with no number', () => {
const argv: Argv = {maxWorkers: undefined};
const argv = {maxWorkers: undefined} as unknown as Config.Argv;
expect(() => check(argv)).toThrow(
'The --maxWorkers (-w) option requires a number to be specified',
);
});

it('raises an exception if config is not a valid JSON string', () => {
const argv: Argv = {config: 'x:1'};
const argv = {config: 'x:1'} as Config.Argv;
expect(() => check(argv)).toThrow(
'The --config option requires a JSON string literal, or a file path with a .js or .json extension',
);
Expand All @@ -63,9 +64,11 @@ describe('check', () => {

describe('buildArgv', () => {
it('should return only camelcased args ', () => {
// @ts-ignore
const mockProcessArgv = jest
.spyOn(process.argv, 'slice')
.mockImplementation(() => ['--clear-mocks']);
// @ts-ignore
const actual = buildArgv(null);
expect(actual).not.toHaveProperty('clear-mocks');
expect(actual).toHaveProperty('clearMocks', true);
Expand Down
Expand Up @@ -3,16 +3,14 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {Argv} from 'types/Argv';

import {Config} from '@jest/types';
import {isJSONString} from 'jest-config';
import isCI from 'is-ci';
import {Options} from 'yargs';

export const check = (argv: Argv) => {
export const check = (argv: Config.Argv) => {
if (argv.runInBand && argv.hasOwnProperty('maxWorkers')) {
throw new Error(
'Both --runInBand and --maxWorkers were specified, but these two ' +
Expand Down Expand Up @@ -69,7 +67,7 @@ export const usage =
'Usage: $0 [--config=<pathToConfigFile>] [TestPathPattern]';
export const docs = 'Documentation: https://jestjs.io/';

export const options = {
export const options: Record<keyof Config.Argv, Options> = {
all: {
default: undefined,
description:
Expand Down
Expand Up @@ -3,32 +3,25 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {AggregatedResult} from 'types/TestResult';
import type {Argv} from 'types/Argv';
import type {GlobalConfig, Path} from 'types/Config';

import path from 'path';
import {Config, TestResult} from '@jest/types';
import {clearLine} from 'jest-util';
import {validateCLIOptions} from 'jest-validate';
import {deprecationEntries} from 'jest-config';
import {runCLI} from '@jest/core';
import * as args from './args';
import chalk from 'chalk';
import exit from 'exit';
import yargs from 'yargs';
import {sync as realpath} from 'realpath-native';
import init from '../init';
import getVersion from '../version';
import * as args from './args';

import {version as VERSION} from '../../package.json';

export async function run(maybeArgv?: Argv, project?: Path) {
export async function run(maybeArgv?: Config.Argv, project?: Config.Path) {
try {
// $FlowFixMe:`allow reduced return
const argv: Argv = buildArgv(maybeArgv);
const argv: Config.Argv = buildArgv(maybeArgv);

if (argv.init) {
await init();
Expand All @@ -48,18 +41,23 @@ export async function run(maybeArgv?: Argv, project?: Path) {
}
}

export const buildArgv = (maybeArgv: ?Argv) => {
export const buildArgv = (maybeArgv?: Config.Argv): Config.Argv => {
const version =
VERSION + (__dirname.includes(`packages${path.sep}jest-cli`) ? '-dev' : '');

const rawArgv: Argv | string[] = maybeArgv || process.argv.slice(2);
const argv: Argv = yargs(rawArgv)
.usage(args.usage)
.version(version)
.alias('help', 'h')
.options(args.options)
.epilogue(args.docs)
.check(args.check).argv;
getVersion() +
(__dirname.includes(`packages${path.sep}jest-cli`) ? '-dev' : '');

const rawArgv: Config.Argv | Array<string> =
maybeArgv || process.argv.slice(2);
const argv =
maybeArgv ||
yargs(process.argv.slice(2))
.usage(args.usage)
.version(version)
.alias('help', 'h')
.options(args.options)
.epilogue(args.docs)
// @ts-ignore: it's unable to infer what arguments it contains
.check(args.check).argv;

validateCLIOptions(
argv,
Expand All @@ -71,16 +69,21 @@ export const buildArgv = (maybeArgv: ?Argv) => {
);

// strip dashed args
return Object.keys(argv).reduce((result, key) => {
if (!key.includes('-')) {
// $FlowFixMe:`allow reduced return
result[key] = argv[key];
}
return result;
}, {});
return Object.keys(argv).reduce(
(result, key) => {
if (!key.includes('-')) {
result[key] = argv[key];
}
return result;
},
{} as Config.Argv,
);
};

const getProjectListFromCLIArgs = (argv, project: ?Path) => {
const getProjectListFromCLIArgs = (
argv: Config.Argv,
project?: Config.Path,
) => {
const projects = argv.projects ? argv.projects : [];

if (project) {
Expand All @@ -104,8 +107,8 @@ const getProjectListFromCLIArgs = (argv, project: ?Path) => {
};

const readResultsAndExit = (
result: ?AggregatedResult,
globalConfig: GlobalConfig,
result: TestResult.AggregatedResult | null,
globalConfig: Config.GlobalConfig,
) => {
const code = !result || result.success ? 0 : globalConfig.testFailureExitCode;

Expand Down Expand Up @@ -140,7 +143,6 @@ const readResultsAndExit = (
'`--detectOpenHandles` to troubleshoot this issue.',
),
);
// $FlowFixMe: `unref` exists in Node
}, 1000).unref();
}
};
Expand Up @@ -3,8 +3,6 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

// TODO: remove exports for the next major
Expand Down
2 changes: 0 additions & 2 deletions packages/jest-cli/src/init/__tests__/init.test.js
Expand Up @@ -3,8 +3,6 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

/* eslint-disable no-eval */
Expand Down
Expand Up @@ -3,7 +3,6 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import modifyPackageJson from '../modify_package_json';
Expand All @@ -13,7 +12,7 @@ test('should remove jest config if exists', () => {
modifyPackageJson({
projectPackageJson: {
jest: {
coverage: true,
collectCoverage: true,
},
},
shouldModifyScripts: true,
Expand Down
Expand Up @@ -3,8 +3,6 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

export const PACKAGE_JSON = 'package.json';
Expand Down
Expand Up @@ -3,32 +3,23 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

export class NotFoundPackageJsonError extends Error {
name: string;
message: string;

constructor(rootDir: string) {
super();
super(`Could not find a "package.json" file in ${rootDir}`);
this.name = '';
this.message = `Could not find a "package.json" file in ${rootDir}`;
Error.captureStackTrace(this, () => {});
}
}

export class MalformedPackageJsonError extends Error {
name: string;
message: string;

constructor(packageJsonPath: string) {
super();
this.name = '';
this.message =
super(
`There is malformed json in ${packageJsonPath}\n` +
'Fix it, and then run "jest --init"';
'Fix it, and then run "jest --init"',
);
this.name = '';
Error.captureStackTrace(this, () => {});
}
}
Expand Up @@ -3,15 +3,14 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import {Config} from '@jest/types';
import {defaults, descriptions} from 'jest-config';

const stringifyOption = (
option: string,
map: Object,
option: keyof Config.InitialOptions,
map: Partial<Config.InitialOptions>,
linePrefix: string = '',
): string => {
const optionDescription = ` // ${descriptions[option]}`;
Expand All @@ -32,7 +31,7 @@ const stringifyOption = (
);
};

const generateConfigFile = (results: {[string]: boolean}): string => {
const generateConfigFile = (results: {[key: string]: unknown}): string => {
const {coverage, clearMocks, environment} = results;

const overrides: Object = {};
Expand All @@ -55,15 +54,21 @@ const generateConfigFile = (results: {[string]: boolean}): string => {
});
}

const overrideKeys: Array<string> = Object.keys(overrides);
const overrideKeys = Object.keys(overrides) as Array<
keyof Config.InitialOptions
>;

const properties: Array<string> = [];

for (const option in descriptions) {
if (overrideKeys.includes(option)) {
properties.push(stringifyOption(option, overrides));
const opt = option as keyof typeof descriptions;

if (overrideKeys.includes(opt)) {
properties.push(stringifyOption(opt, overrides));
} else {
properties.push(stringifyOption(option, defaults, '// '));
properties.push(
stringifyOption(opt, defaults as Config.InitialOptions, '// '),
);
}
}

Expand Down